Какую задачу лучше всего выполнять в стиле функционального программирования?
Недавно я обнаружил стиль функционального программирования, и я убежден, что это уменьшит усилия разработчиков, упростит чтение кода, сделает программное обеспечение более удобным. Однако проблема в том, что я убедил кого-нибудь убедить.
Хорошо, недавно мне была предоставлена возможность поговорить о том, как уменьшить усилия по разработке и поддержке программного обеспечения, и я хотел представить им концепцию функционального программирования и то, как это приносит пользу команде. У меня была идея показать людям 2 набора кода, которые делают точно то же самое, один закодирован очень императивным образом, а другой очень функциональным образом, чтобы показать, что функциональное программирование может сделать код более коротким, понятным и понятным таким образом поддерживаемый. Есть ли такой пример, помимо знаменитой суммы квадратов, например, Лука Болоньезе?
Ответы
Ответ 1
Недавно я обнаружил стиль функционального программирования [...] Ну, недавно мне дали возможность поговорить о том, как уменьшить усилия по разработке программного обеспечения, и я хотел представить концепцию функциональное программирование.
Если вы только что открыли функциональное программирование, я не рекомендую говорить авторитетно по этому вопросу. Я знаю, что в течение первых 6 месяцев, пока я учился F #, весь мой код был только С# с немного более неудобным синтаксисом. Однако после этого периода времени я смог последовательно писать хороший код в идиоматическом, функциональном стиле.
Я рекомендую вам сделать то же самое: подождать 6 месяцев или до тех пор, пока функциональный стиль программирования не станет более естественным, а затем дайте свою презентацию.
Я пытаюсь иллюстрируют преимущества функциональных программирования, и у меня возникла идея показывая людям 2 набора кода, который делает то же самое, один закодированный в очень императивный путь, а другой в очень функциональный способ показать, что функциональное программирование может сделать код путь короче, легче понять и таким образом, поддерживать. Есть ли такой пример, рядом со знаменитой суммой квадратов Например, Лука Болоньезе?
Я представил презентацию F # группе пользователей .NET в своей области, и многие люди в моей группе были впечатлены сопоставлением шаблонов F #. В частности, я показал, как пересечь абстрактное синтаксическое дерево в С# и F #:
using System;
namespace ConsoleApplication1
{
public interface IExprVisitor<t>
{
t Visit(TrueExpr expr);
t Visit(And expr);
t Visit(Nand expr);
t Visit(Or expr);
t Visit(Xor expr);
t Visit(Not expr);
}
public abstract class Expr
{
public abstract t Accept<t>(IExprVisitor<t> visitor);
}
public abstract class UnaryOp : Expr
{
public Expr First { get; private set; }
public UnaryOp(Expr first)
{
this.First = first;
}
}
public abstract class BinExpr : Expr
{
public Expr First { get; private set; }
public Expr Second { get; private set; }
public BinExpr(Expr first, Expr second)
{
this.First = first;
this.Second = second;
}
}
public class TrueExpr : Expr
{
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class And : BinExpr
{
public And(Expr first, Expr second) : base(first, second) { }
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class Nand : BinExpr
{
public Nand(Expr first, Expr second) : base(first, second) { }
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class Or : BinExpr
{
public Or(Expr first, Expr second) : base(first, second) { }
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class Xor : BinExpr
{
public Xor(Expr first, Expr second) : base(first, second) { }
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class Not : UnaryOp
{
public Not(Expr first) : base(first) { }
public override t Accept<t>(IExprVisitor<t> visitor)
{
return visitor.Visit(this);
}
}
public class EvalVisitor : IExprVisitor<bool>
{
public bool Visit(TrueExpr expr)
{
return true;
}
public bool Visit(And expr)
{
return Eval(expr.First) && Eval(expr.Second);
}
public bool Visit(Nand expr)
{
return !(Eval(expr.First) && Eval(expr.Second));
}
public bool Visit(Or expr)
{
return Eval(expr.First) || Eval(expr.Second);
}
public bool Visit(Xor expr)
{
return Eval(expr.First) ^ Eval(expr.Second);
}
public bool Visit(Not expr)
{
return !Eval(expr.First);
}
public bool Eval(Expr expr)
{
return expr.Accept(this);
}
}
public class PrettyPrintVisitor : IExprVisitor<string>
{
public string Visit(TrueExpr expr)
{
return "True";
}
public string Visit(And expr)
{
return string.Format("({0}) AND ({1})", expr.First.Accept(this), expr.Second.Accept(this));
}
public string Visit(Nand expr)
{
return string.Format("({0}) NAND ({1})", expr.First.Accept(this), expr.Second.Accept(this));
}
public string Visit(Or expr)
{
return string.Format("({0}) OR ({1})", expr.First.Accept(this), expr.Second.Accept(this));
}
public string Visit(Xor expr)
{
return string.Format("({0}) XOR ({1})", expr.First.Accept(this), expr.Second.Accept(this));
}
public string Visit(Not expr)
{
return string.Format("Not ({0})", expr.First.Accept(this));
}
public string Pretty(Expr expr)
{
return expr.Accept(this).Replace("(True)", "True");
}
}
class Program
{
static void TestLogicalEquivalence(Expr first, Expr second)
{
var prettyPrinter = new PrettyPrintVisitor();
var eval = new EvalVisitor();
var evalFirst = eval.Eval(first);
var evalSecond = eval.Eval(second);
Console.WriteLine("Testing expressions:");
Console.WriteLine(" First = {0}", prettyPrinter.Pretty(first));
Console.WriteLine(" Eval(First): {0}", evalFirst);
Console.WriteLine(" Second = {0}", prettyPrinter.Pretty(second));
Console.WriteLine(" Eval(Second): {0}", evalSecond);;
Console.WriteLine(" Equivalent? {0}", evalFirst == evalSecond);
Console.WriteLine();
}
static void Main(string[] args)
{
var P = new TrueExpr();
var Q = new Not(new TrueExpr());
TestLogicalEquivalence(P, Q);
TestLogicalEquivalence(
new Not(P),
new Nand(P, P));
TestLogicalEquivalence(
new And(P, Q),
new Nand(new Nand(P, Q), new Nand(P, Q)));
TestLogicalEquivalence(
new Or(P, Q),
new Nand(new Nand(P, P), new Nand(Q, Q)));
TestLogicalEquivalence(
new Xor(P, Q),
new Nand(
new Nand(P, new Nand(P, Q)),
new Nand(Q, new Nand(P, Q)))
);
Console.ReadKey(true);
}
}
}
Приведенный выше код написан в идиоматическом стиле С#. Он использует шаблон посетителя, а не тип-тестирование, чтобы гарантировать безопасность типа. Это около 218 LOC.
Здесь версия F #:
#light
open System
type expr =
| True
| And of expr * expr
| Nand of expr * expr
| Or of expr * expr
| Xor of expr * expr
| Not of expr
let (^^) p q = not(p && q) && (p || q) // makeshift xor operator
let rec eval = function
| True -> true
| And(e1, e2) -> eval(e1) && eval(e2)
| Nand(e1, e2) -> not(eval(e1) && eval(e2))
| Or(e1, e2) -> eval(e1) || eval(e2)
| Xor(e1, e2) -> eval(e1) ^^ eval(e2)
| Not(e1) -> not(eval(e1))
let rec prettyPrint e =
let rec loop = function
| True -> "True"
| And(e1, e2) -> sprintf "(%s) AND (%s)" (loop e1) (loop e2)
| Nand(e1, e2) -> sprintf "(%s) NAND (%s)" (loop e1) (loop e2)
| Or(e1, e2) -> sprintf "(%s) OR (%s)" (loop e1) (loop e2)
| Xor(e1, e2) -> sprintf "(%s) XOR (%s)" (loop e1) (loop e2)
| Not(e1) -> sprintf "NOT (%s)" (loop e1)
(loop e).Replace("(True)", "True")
let testLogicalEquivalence e1 e2 =
let eval1, eval2 = eval e1, eval e2
printfn "Testing expressions:"
printfn " First = %s" (prettyPrint e1)
printfn " eval(e1): %b" eval1
printfn " Second = %s" (prettyPrint e2)
printfn " eval(e2): %b" eval2
printfn " Equilalent? %b" (eval1 = eval2)
printfn ""
let p, q = True, Not True
let tests =
[
p, q;
Not(p), Nand(p, p);
And(p, q),
Nand(Nand(p, q), Nand(p, q));
Or(p, q),
Nand(Nand(p, p), Nand(q, q));
Xor(p, q),
Nand(
Nand(p, Nand(p, q)),
Nand(q, Nand(p, q))
)
]
tests |> Seq.iter (fun (e1, e2) -> testLogicalEquivalence e1 e2)
Console.WriteLine("(press any key)")
Console.ReadKey(true) |> ignore
Это 65 LOC. Поскольку он использует сопоставление шаблонов, а не шаблон посетителя, мы не теряем никакой безопасности типов, и код очень легко читается.
Любой вид символической обработки на порядок проще записывать в F #, чем С#.
[Edit to add:] О, и сопоставление шаблонов - это не просто замена шаблона посетителя, но также позволяет вам сопоставлять с формой данных. Например, здесь функция, которая преобразует Nand в их эквиваленты:
let rec simplify = function
| Nand(p, q) when p = q -> Not(simplify p)
| Nand(Nand(p1, q1), Nand(p2, q2))
when equivalent [p1; p2] && equivalent [q1; q2]
-> And(simplify p1, simplify q1)
| Nand(Nand(p1, p2), Nand(q1, q2))
when equivalent [p1; p2] && equivalent [q1; q2]
-> Or(simplify p1, simplify q1)
| Nand(Nand(p1, Nand(p2, q1)), Nand(q2, Nand(p3, q3)))
when equivalent [p1; p2; p3] && equivalent [q1; q2; q3]
-> Xor(simplify p1, simplify q1)
| Nand(p, q) -> Nand(simplify p, simplify q)
| True -> True
| And(p, q) -> And(simplify p, simplify q)
| Or(p, q) -> Or(simplify p, simplify q)
| Xor(p, q) -> Xor(simplify p, simplify q)
| Not(Not p) -> simplify p
| Not(p) -> Not(simplify p)
Невозможно вообще написать этот код в С#.
Ответ 2
Есть много примеров, но ни один из них не будет столь же впечатляющим, как использование образца, относящегося к одному из ваших проектов на работе. Примеры, такие как "Sum Of Squares" от Luca, являются удивительными, но если кто-то использовал это как доказательство того, как наша база кода может быть написана лучше, я не был бы уверен. Весь пример доказывает, что некоторые вещи лучше написаны функционально. Что вам нужно доказать, ваша база кода лучше написана функционально
Мой совет заключался бы в том, чтобы выбрать некоторые популярные проблемы и некоторые ключевые места в базе кода и переписать их в функциональном стиле. Если вы можете продемонстрировать существенно лучшее решение, это будет долгим путем для победы над коллегами.
Ответ 3
Задачи для функционального стиля? Каждый раз, когда у вас есть общий шаблон кодирования и вы хотите его уменьшить. Некоторое время назад я немного писал об использовании С# для функционального стиля, убедившись, что это практично: Практический функциональный С# (я смущаюсь ссылаться на мой собственный материал здесь, но я думаю, что это актуально в этом случае). Если у вас есть общее "корпоративное" приложение, показывающее, например, то, как выражения прекрасны при сопоставлении с образцом, не будет слишком убедительным.
Но в реальных приложениях есть TONS шаблонов, которые появляются на низком уровне кодирования. Используя функции более высокого порядка, вы можете заставить их уйти. Как я показываю в этом наборе сообщений в блогах, моим любимым примером является шаблон "try-close/finally-abort" WCF. Шаблон "try/finally-dispose" настолько распространен, что он превратился в ключевое слово language: using. То же самое для "замка". Они тривиально представлены как функции более высокого порядка, и только потому, что С# изначально не поддерживали их, нам нужны жестко-кодированные ключевые слова языка для их поддержки. (Quick: переключите блокировку блокировки, чтобы использовать блокировку ReaderWriter. К сожалению, сначала нам нужно написать функцию более высокого порядка.)
Но, возможно, убедительный пример требует взглянуть на Microsoft. Дженерики как параметрический полиморфизм? Это вряд ли OO, но хорошая функциональная концепция, которую теперь любят все. Симпатичная структура Ninject не будет работать без нее. Лямбда? Как деревья выражений, они как LINQ, Fluent NHibernate и т.д. Получают всю свою силу. Опять же, это не происходит от ОО или императивного программирования. Новая библиотека Threading? Довольно уродливый без закрытий.
Таким образом, функциональное программирование в течение последнего десятилетия благословляло такие вещи, как .NET. Основные достижения (например, generics, "LINQ" ) - непосредственно из функциональных языков. Почему бы не осознать, что-то в этом и больше участвовать в этом? Это, как я бы назвал это скептиками.
Большая проблема - это заставить людей совершать прыжок в понимании функций более высокого порядка. Хотя это довольно легко, если вы никогда не видели этого раньше в своей жизни, это может быть шокирующим непонятным. (Похоже, многие считают, что генерики предназначены только для коллекций с типом безопасности, а LINQ - только встроенный SQL.)
Итак, что вам нужно сделать, это пройти через вашу кодовую базу и найти места, которые являются чрезмерно сложным императивным кошмаром. Найдите базовые шаблоны и используйте функции, чтобы хорошо сочетать их. Если вы не можете их найти, вы можете согласиться на просто демонстрацию списков. Например, "найдите все Foos в этом списке и удалите их". Это 1 строка в функциональном стиле "myList.Remove(x = > x.Bla > 0)" по сравнению с 7 строками в стиле С# (создайте список тем, выполните цикл и добавьте в-удалить элементы, зациклируйте и удалите элементы).
Надеемся, что, хотя синтаксис нечетный, люди узнают "ничего себе, это намного проще". Если они могут записать "verbose == more readable" и "выглядит несколько запутанным", у вас будет шанс.
Удачи.
Ответ 4
По сути, функциональная парадигма очень эффективна для параллельной обработки:
"Действительно интересная вещь, которую я хочу вы заметили, что это так скоро как вы думаете о карте и функции, которые каждый может использовать, и они используют их, вам нужно только получить один супергениус для записи жесткого кода для запуска карты и сокращения глобальной массивный параллельный массив компьютеров, и весь старый код, который использовался для работы отлично, когда вы просто запустили петлю работает только на миллион раз быстрее что означает, что он может использоваться для огромные проблемы в одно мгновение.
Повторите лемме. Отвлекаясь само понятие цикла, вы можете реализовать цикл любым способом, который вы хотите, включая его внедрение таким образом который отлично масштабируется с дополнительными аппаратное обеспечение".
http://www.joelonsoftware.com/items/2006/08/01.html
Ответ 5
Лучший пропагандистский документ, когда-либо написанный для функционального стиля, - это статья Джона Хьюза, названная Почему вопросы функционального программирования. Я предлагаю вам сделать несколько примеров для себя, пока не дойдете до сцены, где вы можете убедительно изложить аргументы, изложенные в этой статье.
Многие из примеров в документе являются численными и не резонируют с сегодняшней аудиторией. Еще одно современное упражнение, которое я давал своим ученикам, заключалось в том, чтобы использовать идеи в этой статье для упаковки больших медиафайлов на DVD-диски на 4,7 ГБ для резервного копирования. Они использовали алгоритм поиска пузырьков Майкла Митценмахера для создания альтернативных упаковок, и с использованием этого алгоритма и методов Хьюза было легко получить каждый DVD (за исключением последнего) 99,9%. Очень сладкий.
Ответ 6
Другим примером может быть алгоритм Quicksort. Это можно описать очень кратко на функциональном языке, таком как Haskell:
qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
но для кодирования на итеративном языке требуется еще некоторое кодирование. На ссылочном веб-сайте вы также можете найти множество других примеров при сравнении языков.
Ответ 7
Чтобы достичь того, чего вы хотите, и сообщить об этом другим в вашей организации, вам нужно продемонстрировать, что ваш бизнес-бизнес строится лучше.
Невозможно использовать несколько алгоритмов, чтобы продемонстрировать мощь функционального программирования, если он совершенно бесполезен для вашего бизнес-домена. Итак, возьмите некоторый существующий код и перепишите его функционально. Если вы сможете это доказать, то лучше, люди будут слушать вас - вы показали им конкретный, соответствующий пример. Если вы не можете, возможно, функциональное программирование - это не то решение, которое вы искали.
Ответ 8
Если по "функциональному стилю" вы подразумеваете использование таких понятий, как "map", "apply", "сокращение", "фильтр", лямбда-функции и понимание списков, тогда должно быть очевидно, что код, который должен иметь дело с операциями над списками почти всегда более кратким, когда написано в "функциональном стиле". Но если вы смешиваете "функциональный стиль" с императивным кодом на основном императивном языке, это действительно вопрос стиля.
В python, например, вы можете переопределить Haskell qsort crackmigg следующим образом:
def qsort(list):
if list == []:
return []
else:
x = list[0]; xs = list[1:]
return qsort(filter(lambda y: y<x, xs)) + [x] + qsort(filter(lambda y: y>= x, xs))
Хотя запись последней строки
return qsort([y for y in xs if y<x]) + [x] + qsort([y for y in xs if y>=x])
возможно, более "pythonic".
Но это явно более красноречиво, чем реализация здесь:
http://hetland.org/coding/python/quicksort.html
Что, кстати, так я бы подумал о его реализации, прежде чем я узнал Haskell.
Функциональная версия предельно ясна тогда и только тогда, когда вы приспосабливаетесь к функциональному программированию и пытаетесь понять, что filter
будет делать так же легко, как хорошо изнашиваемый программист на С++ заставляет цикл for
, даже не задумываясь об этом. И это ясно, что здесь стоит настоящая проблема: функциональное программирование "стиля" - совершенно другое мышление. Если люди, с которыми вы работаете, не привыкли к рекурсивному мышлению и не относятся к тому, чтобы волноваться о них, а не только о новой технологии, но и о другом способе мышления о решении своих проблем, тогда любое количество кодовых сравнений не собираюсь их побеждать.
Ответ 9
Хорошим примером может быть создание собственного языка программирования с использованием существующего, где вам нужно будет использовать Monads.
С F # гораздо проще писать логику синтаксического анализа, чем с С#.
Взгляните на эту статью: Функциональные .NET - LINQ или языковые интегрированные монады?
Ответ 10
Алгоритмы, связанные с поиском обратного отслеживания и упрощением поддержки отмены в графических интерфейсах, - это два места, на которых я использовал функциональный стиль на практике.
Ответ 11
Простым примером задачи, которая часто бывает проще всего в функциональном стиле, является преобразование данных из одной формы в другую. "Сумма квадратов" - тривиальный пример преобразования данных. Luca говорит, что в прошлом году PDC показал, как использовать такую трансформацию данных для чего-то более практичного, загружая и анализируя котировки акций. Демонстрация выполняется в F #, но понятия одинаковы и могут применяться к С# или большинству других языков программирования.
http://channel9.msdn.com/pdc2008/TL11/
Ответ 12
Покажите им путь jQuery для итерации элементов DOM:
$(".magic-divs").click(function(){
// FYI, in this context, "this" will be the element clicked on.
alert("somebody clicked on " + this.id);
this.hide();
});
$(".magic-divs").show();
против. как большинство результатов google для "javascript element by classname" делают это:
var arrayOfElements = // this is filled with the elements somehow
for(var i=0,j=arrayOfElements.length; i<j; i++) {
alert("now I get to add an onclick event somehow " + i);
}
// i dont even want to type the ugly for-loop stuff to hide every div...
Функциональное программирование полезно в повседневных вещах, таких как выше!
(примечание: я не знаю, соответствует ли мой пример точному определению функционального программирования, но если это так, то функциональное программирование является удивительным)
Ответ 13
Недавно я придумал небольшой трюк, чтобы сделать лямбда, перешел в мои методы расширения, посмотри больше F # ish.
Вот оно:
Я хотел сделать что-то вроде:
3.Times(() => Console.WriteLine("Doin' it"));
Теперь метод расширения для этого легко реализуется:
public static void Times(this int times, Action action)
{
Enumerable.Range(1, times).ToList().ForEach(index => action());
}
Мне не понравилось, что я указываю индекс здесь: ForEach(index => action())
, хотя он никогда не используется, поэтому я заменил его на _
и получил ForEach(_ => action())
Это хорошо, но теперь я был мотивирован, чтобы мой код вызова выглядел похожим
(Мне никогда не нравилось "()" в начале выражения лямбда), поэтому вместо: 3.Times(() => ...);
мне хотелось 3.Times(_ => ...);
Единственный способ реализовать это - передать поддельный параметр в метод расширения, который никогда не используется, поэтому я изменил его так:
public static void Times(this int times, Action<byte> action)
{
Enumerable.Range(1, times).ToList().ForEach(_ => action(byte.MinValue));
}
Это позволяет мне называть это следующим образом:
3.Times(_ => Console.WriteLine("Doin' it"));
Не так много изменений, но мне все еще понравилось, что можно было так легко сделать эту маленькую настройку, и в то же время удаление "()" шума делает ее более читаемой.
Ответ 14
Не действительно отвечая на вопрос, но это очень хорошая ссылка для тех, кто хочет знать о функциональном программировании на С#
http://blogs.msdn.com/b/ericwhite/archive/2006/10/04/fp-tutorial.aspx
Ответ 15
-
Покажите, как закодировать отдельный массив. В SQL это очень просто, но в массиве памяти было сложно. Теперь легко отличить массив с LINQ.
-
Вы можете объяснить им, что в будущем будет Parralel LINQ (PLINQ). Когда вы начнете писать функциональный код, вам будет проще провести параболизацию вашего приложения. Google широко использует MapReduce.
-
Объясните им, что LINQ - это язык запросов для обработки различных данных. В памяти, в базе данных, excell, веб-сервисах, xml файлах, JSON файлах. Это своего рода универсальный SQL. Однако люди, которые не любят SQL, будут менее убеждены.
Я бы не стал много говорить о функциональном программировании, я бы объяснил, как LINQ может помочь разработчикам.
Ответ 16
Интересно, что никто не ответил на вопрос: какую задачу лучше всего выполнить в "функциональном стиле"?
Программа/алгоритм состоит из двух частей: логического управления и структуры данных. Я думаю, что задачи, которые наилучшим образом выполняются в функциональном стиле, это задействованные списки или массивы в тех случаях, когда они ведут себя как список (например, qsort). Не случайно, что языки функционального программирования имеют очень хорошую поддержку списков.
Когда структуры данных отклоняются от списков, я думаю, что использование стиля функционального программирования становится немного "неестественным".