【问题标题】:How does functional programming avoid state when it seems unavoidable?当函数式编程看起来不可避免时,它如何避免状态?
【发布时间】:2012-10-29 08:55:40
【问题描述】:

假设我们定义了一个函数c sum(a, b),函数式编程风格,它返回其参数的总和。到目前为止,一切都很好; FP的所有优点都没有问题。

现在假设我们在具有动态类型和单例、有状态错误流的环境中运行它。然后假设我们传递了一个a 和/或b 的值,而sum 并非设计用于处理(即不是数字),它需要以某种方式指示错误。

但是怎么做呢?这个功能应该是纯粹的和无副作用的。它如何将错误插入到全局错误流中而不违反它?

【问题讨论】:

  • 这就是为什么通常纯函数和动态类型不能很好地结合在一起。
  • @JakubHampl 是吗?即使在静态类型的语言中,您仍然会遇到运行时错误(例如被零除),所以我看不出这将如何避免 Core Xii 正在谈论的问题(尽管我承认我还没有完全理解这个问题) . Core Xii:错误流到底是什么?
  • @sepp2k 在这个问题的上下文中,“错误流”是一些用于报告错误的全局系统机制,例如std.err、异常等;通常是带外的,但不是必须的。
  • @CoreXii 您是在问如何在(纯)函数式编程语言中进行 IO?如果是这样,不同的语言使用不同的方式,这绝对是重复的。还是您的意思是“我们如何在不移动 IO monad 中的所有功能的情况下实现错误消息(或调试输出)(假设这就是语言实现 IO 的方式)?”
  • @sepp2k 我不知道。我在问如何避免对我来说似乎不可避免的状态。

标签: functional-programming state


【解决方案1】:

我所知道的任何编程语言都没有内置“单一状态错误流”之类的东西,因此您必须制作一个。如果您尝试以纯函数式风格编写程序,您根本不会做出这样的事情。

但是,您可以使用 sum 函数来返回总和或错误指示。实际上,用于执行此操作的类型通常称为 Either。然后,您可以轻松地创建一个函数来调用可能返回错误的一大堆计算,并返回在其他计算中遇到的所有错误的列表。这与您所说的非常接近;它只是显式返回而不是全局的。

请记住,编写函数式程序时的问题是“我如何制作具有我想要的行为的程序?”不是,“我将如何复制另一种编程风格中采用的一种特定方法?”。 “全局状态错误流”是手段而不是结束。您不能拥有纯函数样式的全局有状态错误流,不。但是问问自己,您使用全局有状态错误流来实现的目的是什么;不管是什么,你都可以在函数式编程中实现那个,只是机制不同。

询问纯函数式编程是否可以实现依赖于副作用的特定技术,就像询问您如何在面向对象编程中使用汇编技术一样。 OO 提供了不同的工具供您使用来解决问题;限制自己使用这些工具来模拟不同的工具集并不是使用它们的有效方法。


对 cme​​ts 的回应:如果您希望通过错误流实现将错误消息记录到终端,那么是的,在某种程度上代码正在运行必须做 IO 才能做到这一点。1

打印到终端就像任何其他 IO 一样,没有什么特别之处,因此值得将其作为状态似乎特别不可避免的情况单独列出。因此,如果这将您的问题变成“纯函数式程序如何处理 IO?”,那么毫无疑问,SO 上有很多重复的问题,更不用说许多博客文章和教程正是针对该问题。对于纯编程语言的实现者和用户来说,这并不是一个突然的惊喜,这个问题已经存在了几十年,并且已经在答案中加入了一些相当复杂的想法。

在不同的语言中采用了不同的方法(Haskell 中的IO monad,Mercury 中的独特模式,Haskell 历史版本中的延迟请求和响应流,等等)。基本思想是提出一个可以由纯代码操作的模型,并将模型的操作与语言实现中的实际不纯操作挂钩。这可以让您保持纯度的好处(适用于纯代码但不适用于一般不纯代码的证明仍适用于使用纯 IO 模型的代码)。

必须仔细设计纯模型,以便您实际上无法用它做任何对实际 IO 而言没有意义的事情。例如,Mercury 通过让您编写程序来执行 IO,就像您将宇宙的当前状态作为额外参数传递。这个纯模型准确地表示了依赖和影响程序外部宇宙的操作的行为,但前提是系统中在任何时候都恰好存在宇宙的一种状态,从头到尾贯穿整个程序.所以设置了一些限制

  1. io 类型是抽象的,因此无法构造该类型的值;你能得到一个的唯一方法是从你的来电者那里得到一个。语言实现将 io 值传递到 main 谓词中以启动整个过程。
  2. 传递给mainio 值的模式被声明为唯一。这意味着您不能做可能导致它被复制的事情,例如将其放入容器中或将相同的io 值传递给多个不同的调用。唯一模式确保您只能将 io 值分配给也使用唯一模式的谓词,并且一旦您传递它,一旦该值“死”并且不能在其他任何地方传递。

1 请注意,即使在命令式程序中,如果您让错误记录系统返回错误消息流,然后仅实际决定将它们打印到接近程序的最外层。如果您的日志调用立即直接写入输出,那么我能想到的一些事情在这样的系统中变得更加困难:

  • 推测性地执行计算并通过检查它是否发出任何错误来查看它是否失败
  • 将多个高级系统合并为一个系统,在日志中添加标签以区分各个系统
  • 仅当还有错误消息时才发出调试和信息日志消息(因此,当没有要调试的错误时输出是干净的,当有错误时输出是丰富的)

【讨论】:

  • 我明白你在说什么,但是假设系统有一个终端可以打印消息。在某个地方,一些代码必须触及这个全局的、有状态的对象,不是吗?
  • @CoreXii 是的,一些必要的位本质上是不纯的。我建议你阅读 Simon Peyton Jones 的Tackling the awkward squad。尽管细节现在已经过时,但它是关于 Haskell 如何处理这些问题的非常易读的阐述。其他语言使用不同的方法(我听说过的关键字:唯一性输入、效果系统),但我没有资格谈论这些。
  • @CoreXii 我在回答中添加了一些杂乱无章的内容,希望能解决您的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-23
  • 2010-09-07
  • 2023-03-19
  • 2021-01-11
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
相关资源
最近更新 更多