我最近一直在学习函数式编程(特别是Haskell,但我也经历过关于Lisp和Erlang的教程).虽然我发现这些概念非常具有启发性,但我仍然没有看到"无副作用"概念的实际方面.它有什么实际优势?我试图在功能思维中思考,但是有些情况看起来过于复杂而没有能够以简单的方式保存状态(我不认为Haskell的monad'容易').
是否值得继续深入学习Haskell(或其他纯函数式语言)?功能性或无状态编程实际上是否比程序性更高效?我以后可能继续使用Haskell或其他功能语言,还是应该仅仅为了理解而学习它?
我更关心性能而不是生产力.所以我主要问的是我是否会在函数式语言中比在程序/面向对象/其他方面更高效.
在Nutshell中阅读函数式编程.
无状态编程有很多优点,其中最重要的是显着的多线程和并发代码.说白了,可变状态是多线程代码的敌人.如果值默认是不可变的,程序员不需要担心一个线程在两个线程之间改变共享状态的值,因此它消除了与竞争条件相关的整类多线程错误.由于没有竞争条件,因此也没有理由使用锁,因此不变性也消除了与死锁相关的另一类错误.
这就是函数式编程重要的重要原因,也许是跳过函数式编程序列的最佳理由.也有很多的其它好处,包括简化调试(即函数都是纯和在应用程序的其他部分不发生变异状态),更简洁和表现代码相比,其严重依赖设计模式语言少样板代码,和编译器可以更积极地优化您的代码.
你的程序中的更多部分是无状态的,将部分组合在一起而没有任何中断的方式就越多.无国籍范式的力量不在于无国籍(或纯洁)本身,而在于它使你能够编写强大的,可重复使用的功能并将它们结合起来的能力.
你可以在John Hughes的论文"功能编程为何重要"(PDF)中找到一个很好的教程.
你会采空区更富有成效,特别是如果你选择的是还具有代数数据类型和模式匹配(CAML,SML,哈斯克尔)函数式语言.
许多其他答案都集中在函数式编程的性能(并行性)方面,我认为这非常重要.但是,您确实专门询问了生产力,因为您可以在功能范例中比在命令式范例中更快地编程相同的事物.
我实际上发现(根据个人经验)F#中的编程与我认为更好的方式匹配,因此更容易.我认为这是最大的区别.我已经用F#和C#进行了编程,并且F#中的"语言对抗"要少得多,我很喜欢.您不必考虑F#中的细节.这里有一些我发现我非常喜欢的例子.
例如,即使F#是静态类型的(所有类型都在编译时解析),类型推断也会确定您拥有的类型,因此您不必说出来.如果它无法弄明白,它会自动使你的函数/类/通用.所以你永远不必写任何通用的东西,它都是自动的.我发现这意味着我花更多时间思考问题,而不是如何实现它.事实上,每当我回到C#时,我发现我真的很想念这种类型的推理,你永远不会意识到它是多么分散注意力,直到你不再需要它为止.
同样在F#中,您可以调用函数,而不是编写循环.这是一个微妙的变化,但很重要,因为你不必再考虑循环结构了.例如,这里有一段代码可以通过并匹配某些东西(我不记得是什么,它来自项目的Euler拼图):
let matchingFactors = factors |> Seq.filter (fun x -> largestPalindrome % x = 0) |> Seq.map (fun x -> (x, largestPalindrome / x))
我意识到在C#中做一个过滤器然后一个地图(这是每个元素的转换)会非常简单,但你必须在较低的层次上思考.特别是,您必须编写循环本身,并拥有自己的显式if语句和这些类型的东西.自学习F#以来,我意识到我发现以功能方式编写代码更容易,如果你想过滤,你可以编写"过滤器",如果要映射,你可以编写"map",而不是实现每个细节.
我也喜欢|>运算符,我认为它将f#与ocaml和其他函数语言分开.它是管道运算符,它允许您将一个表达式的输出"管道"到另一个表达式的输入中.它使代码遵循我的想法.就像在上面的代码片段中那样,"采用因子序列,过滤它,然后映射它".这是一个非常高水平的思考,你不会使用命令式编程语言,因为你忙于编写循环和if语句.每当我使用另一种语言时,这是我最想念的一件事.
所以一般来说,即使我可以在C#和F#中编程,我发现使用F#更容易,因为你可以在更高的层次上思考.我认为,因为从函数式编程中删除了较小的细节(至少在F#中),我的工作效率更高.
编辑:我在其中一条评论中看到,您在函数式编程语言中要求提供"状态"的示例.F#可以强制写入,所以这里有一个直接的例子,说明如何在F#中使用可变状态:
let mutable x = 5 for i in 1..10 do x <- x + i
考虑你花了很长时间调试的所有困难错误.
现在,有多少错误是由于程序的两个独立组件之间的"意外交互"造成的?(几乎所有的线程错误有这样的形式:包括写共享数据,死锁的比赛,......另外,是很常见的发现,对全球状态的一些意想不到的效果库,或读/写注册表/环境等) 我我认为至少有三分之一的"难题"属于这一类.
现在,如果你切换到无状态/不可变/纯编程,所有这些错误都会消失.你都带有而不是一些新的挑战(例如,当你做希望不同的模块与环境互动),但在象Haskell语言,这些交互得到明确具体化入式系统,这意味着你可以看的类型关于它可以与程序的其余部分进行的交互类型的功能和原因.
这是"不变性"IMO的重大胜利.在理想的世界中,我们都设计了极好的API,即使事情是可变的,效果也会是局部的,并且记录良好,并且"意外"的交互将保持在最低限度.在现实世界中,有许多API以无数种方式与全球状态相互作用,这些是最有害的错误的来源.渴望无国籍状态有望摆脱组件之间的无意/隐含/幕后交互.
无状态函数的一个优点是它们允许预先计算或缓存函数的返回值.甚至一些C编译器允许您明确地将函数标记为无状态以提高其可优化性.正如许多其他人所指出的那样,无状态函数更容易并行化.
但效率并不是唯一的问题.纯函数更容易测试和调试,因为任何影响它的东西都是明确说明的.当用函数式语言编程时,人们习惯于尽可能少地使用"脏"(使用I/O等)函数.以这种方式分离有状态的东西是设计程序的好方法,即使在功能不太强的语言中也是如此.
功能语言可能需要一段时间来"获取",并且很难向没有经历过该过程的人解释.但是大多数坚持不懈的人终于意识到大惊小怪是值得的,即使他们最终没有使用功能语言.
在没有状态的情况下,自动并行化代码非常容易(因为CPU由越来越多的内核构成,这非常重要).
不久前,我就此主题写了一篇文章:关于纯度的重要性。
当您开始拥有更多流量时,无状态Web应用程序必不可少。
例如,出于安全原因,您可能不想在客户端上存储大量用户数据。在这种情况下,您需要将其存储在服务器端。您可以使用Web应用程序的默认会话,但是如果您有多个应用程序实例,则需要确保每个用户始终被定向到同一实例。
负载平衡器通常具有“粘性会话”的能力,其中负载平衡器以某种方式知道向用户发送请求的服务器。但是,这并不理想,例如,这意味着每次您重新启动Web应用程序时,所有连接的用户都将丢失会话。
更好的方法是将会话存储在Web服务器后的某种数据存储中,这些天来,有很多很棒的nosql产品(redis,mongo,elasticsearch,memcached)可用。这样,Web服务器是无状态的,但您仍具有服务器端的状态,可以通过选择正确的数据存储设置来管理此状态的可用性。这些数据存储区通常具有很大的冗余性,因此几乎应该始终可以在不影响用户的情况下对Web应用程序甚至数据存储区进行更改。