首页 文章

无状态编程的优点?

提问于
浏览
122

我最近一直在学习函数式编程(特别是Haskell,但我也经历过关于Lisp和Erlang的教程) . 虽然我发现这些概念非常具有启发性,但我仍然没有看到“无副作用”概念的实际方面 . 它有什么实际优势?我试图在功能性思维中思考,但是有些情况看起来过于复杂而没有能够以简单的方式保存状态(我不认为Haskell的monads很容易') .

是否值得继续深入学习Haskell(或其他纯函数式语言)?功能性或无状态编程实际上比程序性更有效吗?我可能会在以后继续使用Haskell或其他功能语言,还是应该仅仅为了理解而学习它?

我更关心性能而不是 生产环境 力 . 所以我主要问的是我是否会在函数式语言中比在程序/面向对象/其他方面更高效 .

8 回答

  • 41

    阅读Functional Programming in a Nutshell .

    无状态编程有很多优点,其中最重要的是显着的多线程和并发代码 . 说白了,可变状态是多线程代码的敌人 . 如果默认情况下值是不可变的,那么程序员也没有理由使用锁,因此不可变性也消除了与死锁相关的另一类错误 .

    这就是函数式编程重要的重要原因,也许是跳过函数式编程序列的最佳理由 . 还有许多其他好处,包括简化调试(即函数是纯粹的,不会在应用程序的其他部分中改变状态),更简洁和富有表现力的代码,与严重依赖于设计模式的语言相比,样板代码更少,以及编译器可以更积极地优化您的代码 .

  • 3

    你的程序越多,无状态, the more ways there are to put pieces together without having anything break . 无国籍范式的力量不在于无国籍(或纯洁)本身,而在于它使你能够编写强大的,可重复使用的功能并将它们结合起来的能力 .

    你可以在John Hughes的论文Why Functional Programming Matters(PDF)中找到一个包含大量例子的好教程 .

    如果您选择具有代数数据类型和模式匹配的函数式语言(Caml,SML,Haskell),您将会更加高效 .

  • 4

    许多其他答案都集中在函数式编程的性能(并行性)方面,我认为这非常重要 . 但是,您确实专门询问了 生产环境 力,因为您可以在功能范例中比在命令式范例中更快地编程相同的事物 .

    我实际上发现(根据个人经验)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#中做一个过滤器然后一个 Map (这是每个元素的转换)会非常简单,但你必须在较低的层次上思考 . 特别是,您必须编写循环本身,并拥有自己的显式if语句和这些类型的东西 . 自从学习F#以来,我意识到我发现以功能方式编写代码更容易,如果你想过滤,你可以编写“过滤器”,如果你想要映射,你可以编写“map”,而不是实现每个细节 .

    我也喜欢|>运算符,我认为它将f#与ocaml和其他函数语言分开 . 它是管道运算符,它允许您将一个表达式的输出“管道”到另一个表达式的输入中 . 它使代码遵循我的想法 . 就像在上面的代码片段中那样,“采用因子序列,过滤它,然后映射它” . 这是一个非常高水平的思考,你没有使用命令式编程语言,因为你忙于编写循环和if语句 . 这是我想念的一件事每当我进入另一种语言时,大多数

    所以一般来说,即使我可以在C#和F#中编程,我发现使用F#更容易,因为你可以在更高的层次上思考 . 我认为,因为从函数式编程中删除了较小的细节(至少在F#中),我的工作效率更高 .

    Edit :我在其中一条评论中看到,您在函数式编程语言中要求提供"state"的示例 . F#可以强制写入,所以这里有一个直接的例子,说明如何在F#中使用可变状态:

    let mutable x = 5
    for i in 1..10 do
        x <- x + i
    
  • 13

    考虑一下你花了很长时间调试的所有困难错误 .

    现在,这些错误中有多少是由程序的两个独立组件之间的"unintended interactions"引起的? (几乎所有的线程错误都有这种形式:涉及编写共享数据,死锁等的竞赛......此外,通常会发现对全局状态有一些意外影响的库,或者读/写注册表/环境等等)我认为至少有1/3 'hard bugs'属于这个类别 .

    现在,如果你切换到无状态/不可变/纯编程,所有这些错误都会消失 . 您会遇到一些新的挑战(例如,当您希望不同的模块与环境交互时),但是在像Haskell这样的语言中,这些交互会明确地被引入类型系统,这意味着您只需查看类型关于它可以与程序的其余部分进行的交互类型的功能和原因 .

    这是“不变性”IMO的重大胜利 . 在理想的世界中,我们都设计了极好的API,即使事物是可变的,效果也会是局部的,并且记录良好,并且“意外”的交互将保持在最低限度 . 在现实世界中,有许多API以无数种方式与全球状态相互作用,这些是最有害的错误的来源 . 渴望无国籍状态有望摆脱组件之间的无意/隐含/幕后交互 .

  • 19

    无状态函数的一个优点是它们允许预先计算或缓存函数的返回值 . 甚至一些C编译器也允许您明确地将函数标记为无状态以提高其可优化性 . 正如许多其他人所指出的那样,无状态函数更容易并行化 .

    但效率并不是唯一的问题 . 纯函数更容易测试和调试,因为任何影响它的东西都是明确说明的 . 当用函数式语言编程时,人们习惯于尽可能少地使用“脏”(使用I / O等)函数 . 以这种方式分离有状态的东西是设计程序的好方法,即使在不那么功能的语言中也是如此 .

    功能语言可能需要一段时间来“获取”,并且很难向没有经历过该过程的人解释 . 但是大多数坚持不懈的人终于意识到大惊小怪是值得的,即使他们最终没有使用功能语言 .

  • 154

    在没有状态的情况下,自动并行化代码非常容易(因为CPU由越来越多的内核组成,这非常重要) .

  • 6

    我刚刚写了一篇关于这个主题的帖子:On The Importance of Purity .

  • 5

    当您开始拥有更高的流量时,无状态Web应用程序至关重要 .

    出于安全原因,可能存在大量您不希望存储在客户端的用户数据 . 在这种情况下,您需要将其存储在服务器端 . 您可以使用Web应用程序默认会话,但如果您有多个应用程序实例,则需要确保每个用户始终定向到同一实例 .

    负载均衡器通常具有“粘性会话”的能力,其中负载均衡器可以知道哪个服务器发送用户请求 . 但这并不理想,例如,这意味着每次重新启动Web应用程序时,所有连接的用户都将丢失会话 .

    一种更好的方法是将Web服务器后面的会话存储在某种数据存储中,现在有很多很好的nosql产品可用于此(redis,mongo,elasticsearch,memcached) . 这样,Web服务器是无状态的,但您仍然具有状态服务器端,并且可以通过选择正确的数据存储设置来管理此状态的可用性 . 这些数据存储通常具有很强的冗余性,因此几乎总是可以对Web应用程序甚至数据存储进行更改,而不会影响用户 .

相关问题