代码之家  ›  专栏  ›  技术社区  ›  Sasha Chedygov

无状态编程的优点?

  •  153
  • Sasha Chedygov  · 技术社区  · 17 年前

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

    10 回复  |  直到 14 年前
        1
  •  177
  •   Dave Jarvis James Eichele    12 年前

    Functional Programming in a Nutshell .

    无状态编程有很多优点,尤其是 显著地 多线程和并发代码。坦率地说,可变状态是多线程代码的敌人。如果默认情况下值是不可变的,程序员就不需要担心一个线程在两个线程之间改变共享状态的值,因此它消除了与竞争条件相关的一整类多线程错误。由于没有竞争条件,也没有理由使用锁,因此不变性也消除了与死锁相关的另一类bug。

    这就是函数式编程重要的原因,也可能是跳上函数式编程训练的最佳原因。还有很多其他好处,包括简化调试(即函数是纯的,不会在应用程序的其他部分改变状态),更简洁、更具表现力的代码,与严重依赖设计模式的语言相比,样板代码更少,编译器可以更积极地优化代码。

        2
  •  48
  •   Sasha Chedygov    14 年前

    程序中无状态的部分越多, 有更多的方法可以把碎片拼凑在一起,而不会有任何破损 无国籍范式的力量不在于无国籍(或纯洁) 可重复使用的

    你可以在约翰·休斯的论文中找到一个很好的教程,里面有很多例子 Why Functional Programming Matters (PDF)。

    你会的 大量 更高效,特别是如果你选择一种也有代数数据类型和模式匹配的函数式语言(Caml、SML、Haskell)。

        3
  •  20
  •   Ray Hidayat    17 年前

    许多其他答案都集中在函数式编程的性能(并行性)方面,我认为这非常重要。然而,你确实特别询问了生产力,比如,在函数式范式中,你能比在命令式范式中更快地编写同样的东西吗。

    我实际上发现(根据个人经验)用F#编程更符合我的想法,所以更容易。我认为这是最大的区别。我用F#和C#都编程过,在F#中“对抗语言”的次数要少得多,这是我所喜欢的。你不必考虑F#中的细节。以下是我发现自己真正喜欢的一些例子。

    同样在F#中,不是编写循环,而是调用函数。这是一个微妙的变化,但意义重大,因为你不必再考虑循环构造了。例如,这里有一段代码,它将遍历并匹配一些东西(我不记得是什么,它来自一个欧拉谜题项目):

    let matchingFactors =
        factors
        |> Seq.filter (fun x -> largestPalindrome % x = 0)
        |> Seq.map (fun x -> (x, largestPalindrome / x))
    

    我意识到,在C#中先做一个过滤器,然后再做一个映射(这是每个元素的转换)会很简单,但你必须从较低的层次思考。特别是,你必须自己编写循环,并有自己的显式if语句,以及诸如此类的东西。自从学习F#以来,我意识到我发现用函数式编码更容易,如果你想过滤,你可以编写“过滤器”,如果你想要映射,你可以写“映射”,而不是实现每个细节。

    我也喜欢|>运算符,我认为它将F#与ocaml以及可能的其他函数式语言分开。这是管道运算符,它允许您将一个表达式的输出“管道”到另一个表达式。它使代码更符合我的想法。就像上面的代码片段一样,也就是说,“取因子序列,过滤它,然后映射它。”这是一种非常高层次的思考,这在命令式编程语言中是不可能的,因为你忙于编写循环和if语句。每当我学习另一种语言时,这是我最想念的一件事。

    let mutable x = 5
    for i in 1..10 do
        x <- x + i
    
        4
  •  15
  •   Brian    17 年前

    考虑一下你花了很长时间调试的所有困难的bug。

    现在,这些错误中有多少是由于程序的两个独立组件之间的“意外交互”造成的?(几乎所有线程错误都有这种形式:涉及写入共享数据的竞争、死锁……此外,还经常发现对全局状态或读/写注册表/环境有一些意外影响的库等。) 一、 假设至少三分之一的“硬bug”属于这一类。

    现在,如果你切换到无状态/不可变/纯编程,所有这些错误都会消失。相反,你面临着一些新的挑战(例如,当你

        5
  •  8
  •   Peter Mortensen Pieter Jan Bonestroo    15 年前

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

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

    函数式语言可能需要一段时间才能“理解”,而且很难向没有经历过这个过程的人解释。但大多数坚持了足够长时间的人最终意识到,即使他们最终不太使用函数式语言,这种大惊小怪也是值得的。

        6
  •  7
  •   Zifre    17 年前

        7
  •  6
  •   shmish111    12 年前

    当你开始有更高的流量时,无状态的web应用程序是必不可少的。

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

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

        8
  •  5
  •   naasking    16 年前

    对象

    describe("counter", () => {
        it("should increment the count by one when 'increment' invoked without 
        argument", () => {
           const counter = new Counter(0)
           counter.increment()
           expect(counter.count).toBe(1)
        })
       it("should increment the count by n when 'increment' invoked with 
        argument", () => {
           const counter = new Counter(0)
           counter.increment(2)
           expect(counter.count).toBe(2)
        })
    })
    

    功能的

     describe("incrementNumberBy(startingNumber, increment)", () => {
    
       it("should increment by 1 if n not supplied"){
          expect(incrementNumberBy(0)).toBe(1)
       }
    
       it("should increment by 1 if n = 1 supplied"){
          expect(countBy(0, 1)).toBe(1)
       }
    
     })
    
    

    由于函数没有状态,并且输入的数据更明确,因此当您试图找出测试可能失败的原因时,需要关注的事情更少。关于我们必须做的柜台测试

           const counter = new Counter(0)
           counter.increment()
           expect(counter.count).toBe(1)
    

    前两行都有助于 counter.count 在这样一个简单的例子中,1行与2行可能有问题的代码并不是什么大问题,但当你处理一个更复杂的对象时,你可能会给你的测试增加很多复杂性。

    相比之下,当你用函数式语言编写项目时,它会促使你保持花哨的算法依赖于流入和流出特定函数的数据,而不是依赖于系统的状态。

    另一种看待它的方式是说明在每种范式中测试系统的心态。

    对于面向对象编程:在对对象的状态执行Y和Z操作后,确保对象A的方法在给定输入参数X的情况下正常工作。在对对象的状态执行W和Y操作后,确保对象B的方法在给定输入参数X的情况下正常工作。

        9
  •  2
  •   jjhiggz    5 年前

    无状态编程的优点与无跳转编程的优点相吻合,只是更为明显。

    尽管许多函数式编程的描述都强调缺乏变异,但缺乏变异也与缺乏无条件控制传输(如循环)密不可分。在函数式编程语言中,递归,特别是尾部递归,取代了循环。递归消除了无条件控制结构

    “肆无忌惮地使用 首选 声明的直接后果是,很难找到一组有意义的坐标来描述流程的进展。"

    然而,Dijkstra的观察仍然适用于避免 首选 ,因为这样的陈述 , 如果 什么都只是装点门面 首选 首选 给套笼头 首选 仍然存在所有相同的问题。

    例如,为了说服自己递归函数是正确的,我们可以验证它的基本情况,然后理解和检验它的归纳假设。

    首选 如果 , 以及其他结构。

    然而,相比之下,在函数式程序中,没有任何变量的先验值可供推理;那类问题都解决了。