3

假设我们定义了一个函数c sum(a, b),函数式编程风格,它返回其参数的总和。到现在为止还挺好; FP的所有美好事物都没有任何问题。

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

但是怎么做?这个函数应该是纯粹的和无副作用的。它如何在不违反该规则的情况下将错误插入到全局错误流中?

4

1 回答 1

9

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

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

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

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


回应评论:如果你想用你的错误流实现的是将错误消息记录到终端,那么是的,在某种程度上,代码将不得不做 IO 来做到这一点。1

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

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

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

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

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

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