在函数式编程风格中最好完成哪些任务?

我刚刚发现了函数编程风格,我相信它会减少开发工作,使代码更容易阅读,使软件更易于维护。 然而,问题是我在说服任何人。

好吧,最近我有机会就如何减少软件开发和维护工作发表演讲,我想向他们介绍函数式编程的概念以及它如何使团队受益。 我有这样的想法,即向人们展示两组完全相同的代码,一个以非常强制的方式编码,另一个以非常实用的方式编写,以表明函数式编程可以使代码更简单,更容易理解和因此可维护。 有没有这样的例子,除了Luca Bolognese的着名的正方形例子之外?

我刚刚发现函数式编程风格[…]嗯,最近我有机会就如何减少软件开发工作进行讨论,我想介绍函数式编程的概念。

如果你刚刚发现函数式编程,我建议尝试就这个主题进行权威性的讨论。 我知道在我学习F#的前6个月里,我的所有代码都只是C#,语法稍微笨拙。 然而,在那段时间之后,我能够以惯用的function风格编写一致的优秀代码。

我建议你这样做:等待6个月左右,直到函数式编程风格更自然地出现,然后给你的演示文稿。

我试图说明函数式编程的好处,我有想法向人们展示两组代码执行相同的操作,一组以非常强制的方式编码,另一组以非常实用的方式编写,以显示函数式编程可以使代码更简单,更容易理解,从而维护。 是否有这样的例子,除了卢卡博洛尼塞的着名的平方和例子?

我向我所在地区的.NET用户组发了一个F#演示文稿,我小组中的很多人都对F#的模式匹配印象深刻。 具体来说,我展示了如何遍历C#和F#中的抽象语法树:

using System; namespace ConsoleApplication1 { public interface IExprVisitor { 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(IExprVisitor 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(IExprVisitor visitor) { return visitor.Visit(this); } } public class And : BinExpr { public And(Expr first, Expr second) : base(first, second) { } public override t Accept(IExprVisitor visitor) { return visitor.Visit(this); } } public class Nand : BinExpr { public Nand(Expr first, Expr second) : base(first, second) { } public override t Accept(IExprVisitor visitor) { return visitor.Visit(this); } } public class Or : BinExpr { public Or(Expr first, Expr second) : base(first, second) { } public override t Accept(IExprVisitor visitor) { return visitor.Visit(this); } } public class Xor : BinExpr { public Xor(Expr first, Expr second) : base(first, second) { } public override t Accept(IExprVisitor visitor) { return visitor.Visit(this); } } public class Not : UnaryOp { public Not(Expr first) : base(first) { } public override t Accept(IExprVisitor visitor) { return visitor.Visit(this); } } public class EvalVisitor : IExprVisitor { 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 { 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); } } } 

上面的代码是用惯用的C#风格编写的。 它使用访客模式而不是类型测试来保证类型安全。 这大概是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 (^^) pq = 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#更容易用F#写入。

[编辑添加:]哦,模式匹配不仅仅是访问者模式的替代品,它还允许您匹配数据的形状 。 例如,这是一个将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) 

不可能在C#中简明扼要地编写这段代码。

有很多例子,但没有一个像使用与你工作的一个项目相关的样本那样充满影响力。 像Luca的“Sum Of Squares”这样的例子很棒但是如果有人用它作为我们的代码库如何写得更好的证据我就不会相信。 所有的例子certificate有些东西在function上写得更好。 您需要certificate的是您的代码库在function上更好地编写

我的建议是选择一些流行的麻烦点和代码库中的一些核心点,并以function样式重写它们。 如果您能够展示出更好的解决方案,那么赢得同事将会有很长的路要走。

function风格的任务? 任何时候你有一个共同的编码模式,并希望减少它。 不久之前,我写了一些关于使用C#的function样式,同时确保它是实用的: 实用functionC# (我很犹豫,在这里链接到我自己的东西,但我认为这在这种情况下是相关的)。 如果您有一个共同的“企业”应用程序,那么显示表达式在模式匹配中的可爱程度将不会太令人信服。

但在现实世界的应用程序中,有很多模式会以较低的编码级别出现。 使用更高阶的函数,你可以让它们消失。 正如我在那篇博文中所展示的那样,我最喜欢的例子是WCF的“try-close / finally-abort”模式。 “try / finally-dispose”模式非常常见,它变成了一个语言关键字:using。 “锁定”也是一样。 这些都被简单地表示为高阶函数,并且仅仅因为C#最初不支持它们,我们需要硬编码语言关键字来支持它们。 (快速:切换你的“锁定”块以使用ReaderWriter锁。糟糕,我们必须先写一个更高阶的function。)

但也许令人信服只需要看微软。 generics又是参数多态? 这不是OO,而是一个很好的function概念,现在,每个人都喜欢。 如果没有它,可爱的Ninject框架将无法运行。 Lambda表达式? 作为表达树,他们是如何LINQ,Fluent NHibernate等获得他们所有的力量。 同样,这不是来自OO或命令式编程。 新的线程库? 非常丑陋没有封闭。

因此,在过去十年中,函数式编程一直在为.NET之类的事物带来祝福。 主要的进步(例如generics,“LINQ”)直接来自函数式语言。 为什么不意识到它有什么东西并且更多地参与其中? 这就是我对怀疑论者的看法。

更大的问题实际上是让人们在理解更高阶函数方面有所突破。 虽然它很容易,如果你以前从未见过它,那可能会令人难以理解。 (哎呀,似乎很多人认为generics只是用于类型安全的集合,而LINQ只是嵌入式SQL。)

所以,你应该做的就是浏览你的代码库,找到一个过于复杂的必然噩梦。 搜索底层模式,并使用函数将它们很好地串在一起。 如果你找不到任何东西,你可能会满足于只是演示关闭列表。 例如,“在此列表中找到所有Foos并将其删除”。 function样式“myList.Remove(x => x.Bla> 0)”中的1行与C#样式中的7行相比(创建临时列表,循环并添加删除项,循环并删除项)。

希望是,即使语法很奇怪,人们也会认识到“哇,这更简单”。 如果他们可以放下“详细= =更具可读性”和“看起来令人困惑”,你就有机会。

祝好运。

从本质上讲,function范例对并行处理非常有效:

“我想让你注意到的真正有趣的事情是,只要你想到map并将其缩减为每个人都可以使用的函数,并且他们使用它们,你只需要一个supergenius就可以编写硬代码来运行在全局大规模并行计算机arrays上进行映射和缩减,以及在运行循环时过去工作正常的所有旧代码仍然只能运行数十亿次,这意味着它可以用于瞬间解决大问题。

勒米重复一遍。 通过抽象出循环的概念,你可以以任何你想要的方式实现循环,包括以一种可以与额外硬件很好地扩展的方式实现循环。

http://www.joelonsoftware.com/items/2006/08/01.html

为function风格编写的最佳宣传论文是John Hughes撰写的一篇名为Why Functional Programming Matters的论文 。 我建议你自己做一些例子,直到你达到可以令人信服地在论文中提出论点的阶段。

本文中的许多例子都是数字的,不会与今天的观众产生共鸣。 我给学生们的另一个现代练习是使用那篇论文中的想法将大型媒体文件打包到4.7GB DVD上进行备份。 他们使用Michael Mitzenmacher的“泡泡搜索”算法来生成替代包装,并且使用这种算法和Hughes的技术,很容易让每张DVD(除了最后一张)满99.9%。 很甜。

另一个例子是Quicksort算法 。 它可以用像Haskell这样的函数式语言进行简要描述:

 qsort [] = [] qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs) 

但需要在迭代语言中进行更多编码。 在引用的网站上,您还可以找到许多其他语言比较的例子。

为了实现您的目标,并将其传达给组织中的其他人,您需要certificate公司的业务是以更好的方式构建的。

如果它对您的业务领域完全无用,那么使用几种算法来展示函数式编程的强大function是没有用的。 因此,请使用一些现有代码并在function上重写它。 如果你能certificate这一点,那就更好了,人们会听你的 – 你已经向他们展示了一个具体的,相关的例子。 如果你不能,那么函数式编程可能不是你一直在寻找的解决方案。

如果用’function样式’表示使用’map’,’apply’,’reduce’,’filter’,lambda函数和列表推导等概念,那么很明显那些必须处理操作的代码使用“function样式”编写时,列表几乎总是更简洁。 但是,如果你将“function风格”与命令式代码混合在一种大多数命令式语言中,那真的只是风格问题。

例如,在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)) 

虽然把最后一行写成了

 return qsort([y for y in xs if y=x]) 

可以说更像是“pythonic”。

但这显然比这里的实现简洁:

http://hetland.org/coding/python/quicksort.html

顺便提一下,在我学习Haskell之前,我是如何考虑实现它的。

function版本非常清晰, 仅当你适应了函数式编程并且很容易理解filter将要做的事情,就像老旧的C ++程序员在没有考虑它的情况下进行for循环一样容易。 这显然是真正的问题所在:function性“风格”编程是一种完全不同的心态 。 如果与你一起工作的人不习惯递归思考,并且不是那种兴奋的类型,不仅仅是一种新技术,而是一种思考解决问题的另一种方式,那么任何数量的代码比较都不是不会赢得他们。

一个很好的例子是使用现有的编程语言创建自己的编程语言,您必须使用Monads 。

使用F#,编写解析逻辑要比使用C#简单得多。

看看这篇文章: function.NET – LINQ或语言集成Monads?

涉及回溯搜索和简化GUI中撤消支持的算法是我在实践中使用function样式的两个地方。

通常在function样式中最容易完成的任务的简单示例是将数据从一种forms转换为另一种forms。 “平方和”是数据转换的一个简单例子。 Luca在去年的PDC演讲中展示了如何使用这种数据转换来实现更实用,下载和分析股票报价。 演示在F#中完成,但概念是相同的,可以应用于C#或大多数其他编程语言。

http://channel9.msdn.com/pdc2008/TL11/

向他们展示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(); 

与大多数谷歌搜索“javascript element by classname”的结果相比:

 var arrayOfElements = // this is filled with the elements somehow for(var i=0,j=arrayOfElements.length; i 

函数式编程在上面的日常事务中很有用!

(注意:我不知道我的例子是否符合函数式编程的确切定义,但如果确实如此,那么函数式编程就很棒了)

我最近想出了一个小技巧来制作lambdas,传递给我的扩展方法看起来更像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())

这很好,但现在我有动力让我的调用代码看起来很相似

(我从不喜欢lambda表达式开头的“()”),而不是: 3.Times(() => ...); 我想要3.Times(_ => ...); 实现这一点的唯一方法是将一个假参数传递给扩展方法,该方法永远不会被使用,所以我修改它是这样的:

  public static void Times(this int times, Action action) { Enumerable.Range(1, times).ToList().ForEach(_ => action(byte.MinValue)); } 

这允许我像这样调用它:

 3.Times(_ => Console.WriteLine("Doin' it")); 

没有太大的改变,但我仍然很享受,可以轻松地进行这种小调整,同时去除“()”噪音使其更具可读性。

并没有真正回答这个问题,但对于那些想要了解C#中的函数式编程的人来说,这是一个非常好的链接

http://blogs.msdn.com/b/ericwhite/archive/2006/10/04/fp-tutorial.aspx

  1. 演示如何编码不同的数组。 SQL中的区别非常简单,但在内存arrays上却很难。 现在很容易用LINQ区分数组。

  2. 您可以向他们解释将来会有parralel LINQ(PLINQ)。 当您开始编写function代码时,您可以更轻松地对应用程序进行分类。 Google广泛使用MapReduce。

  3. 向他们解释LINQ是一种操纵不同类型数据的查询语言。 在内存中,在数据库中,excell,web服务,xml文件,JSON文件。 它是某种通用SQL。 然而,不喜欢SQL的人会更不相信。

我不会谈论函数式编程,我会解释LINQ如何帮助开发人员。

有趣的是,没有人真正回答过这个问题:什么样的任务最好用“function风格”来完成?

程序/算法由两部分组成:逻辑控制和数据结构。 我认为在function样式中最好的任务是那些涉及列表或数组的行为,就像它们的行为类似于列表(例如qsort)。 函数式编程语言对列表有很好的支持并非巧合。

当数据结构偏离列表时,我认为函数式编程风格的使用变得有点“不自然”。