如何在纯函数式编程中执行副作用?

时间:2013-08-11 14:24:26

标签: functional-programming purely-functional

我现在正在处理函数式编程的概念,并且发现它非常有趣,引人入胜且令人兴奋。特别是纯函数的概念在各方面都很棒。

但有一件事我没有得到:如何在限制自己使用纯粹的功能时处理副作用。

例如,如果我想计算两个数字的总和,我可以编写一个纯函数(在JavaScript中):

var add = function (first, second) {
  return first + second;
};

完全没问题。但是如果我想将结果打印到控制台怎么办? “根据定义将某些东西打印到控制台”的任务并不纯粹 - 但我怎么能/应该用纯函数式编程语言来处理它呢?

3 个答案:

答案 0 :(得分:15)

有一些方法可以解决这个问题。你必须要接受的一件事是,在某些时候,存在一种神奇的不纯机器,它采用纯粹的表达方式,并通过与环境的交互使它们变得不纯洁。你不应该问这个神奇的机器。

我能想到两种方法。至少存在我忘记的第三个。


I / O Streams

最容易理解的方法可能是流I / O.您的main函数有一个参数:系统上发生的事物流 - 包括按键,文件系统上的文件等等。您的main函数也会返回一件事:您希望在系统上发生的事情流。

Streams就像列表一样,请注意,只有您可以一次构建一个元素,并且一旦构建了元素,接收者就会收到元素。您的纯程序从这样的流中读取,并在希望系统执行某些操作时附加到自己的流中。

使所有这些工作的胶水是一个神奇的机器,位于程序之外,从“请求”流中读取并将内容放入“答案”流中。虽然你的程序是纯粹的,但这个神奇的机器不是。

输出流可能如下所示:

[print('Hello, world! What is your name?'), input(), create_file('G:\testfile'), create_file('C:\testfile'), write_file(filehandle, 'John')]

并且相应的输入流将是

['John', IOException('There is no drive G:, could not create file!'), filehandle]

查看流出的input如何导致'John'出现在插播广告中?这就是原则。

Monadic I / O

Monadic I / O就是Haskell所做的,并且做得非常好。您可以将此想象为构建一个巨大的I / O命令树,让操作员将它们粘合在一起,然后您的main函数将这个庞大的表达式返回到位于程序外部的神奇机器并执行命令并执行操作表明。这个神奇的机器是不纯净的,而你的表达式构建程序是纯粹的。

您可能想要将此命令树看作类似

的内容
main
  |
  +---- Cmd_Print('Hello, world! What is your name?')
  +---- Cmd_WriteFile
           |
           +---- Cmd_Input
           |
           +---+ return validHandle(IOResult_attempt, IOResult_safe)
               + Cmd_StoreResult Cmd_CreateFile('G:\testfile') IOResult_attempt
               + Cmd_StoreResult Cmd_CreateFile('C:\testfile') IOResult_safe

它首先要打印一个问候语。它接下来要做的是它想要写一个文件。为了能够写入文件,它首先需要从输入中读取它应该写入文件的内容。然后它应该有一个文件句柄写入。它从名为validHandle的函数中获取此函数,该函数返回两个备选项的有效句柄。通过这种方式,您可以将看起来不纯的代码与看起来像纯代码的东西混合在一起。


这个“解释”接近于询问关于你不应该提出问题的神奇机器的问题,所以我将用一些智慧来解决这个问题。

  • 真正的monadic I / O看起来不像我的例子。我的例子是monadic I / O如何在不破坏纯度的情况下看起来像“引擎盖”的可能解释之一。

  • 尝试使用我的示例来了解如何使用纯I / O.引擎盖下的某些东西与你如何使用它完全不同。如果你以前从未见过汽车,那么通过阅读其中一个蓝图,你就不会成为一个好的驾驶员。

    我一直说你不应该问一些关于实际做事的魔法机器的问题是,当程序员学习东西的时候,他们往往想要在机器上捅一下来试图找出答案。我不建议对纯I / O这样做。机器可能不会教你如何使用不同的I / O变种。

    这类似于通过查看反汇编的JVM字节码来学习Java的方法。

  • 学习使用monadic I / O和基于流的I / O.这是一种很酷的体验,在你的工具带下有更多的工具总是好的。

答案 1 :(得分:5)

Haskell是一种纯函数式语言,使用“monad”处理“不纯”函数。 monad基本上是一种模式,可以通过延续传递轻松链接函数调用。从概念上讲,Haskell中的print函数基本上有三个参数:要打印的字符串,程序的状态以及程序的其余部分。它在调用程序的新状态时调用程序的其余部分,其中字符串在屏幕上。这样就没有修改任何州。

对于monad如何工作有许多深入的解释,因为出于某种原因,人们认为这是一个难以理解的概念:事实并非如此。您可以通过互联网搜索找到很多,我认为这是我最喜欢的一个:http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html

答案 2 :(得分:1)

kqr:

至少我忘记了三分之一。

继续尝试可能是您想要回忆的。

乔尼:

从概念上讲,Haskell中的打印功能基本上需要三个 参数:要打印的字符串,程序状态, 以及该程序的其余部分。

您已经拥有了更多功能:不需要该额外的状态参数; 拥有该程序的其余部分就足够了(这是实现的关键 与状态)。参见How to Declare an Imperative的3.2(p18)节 Philip Wadler提供的详细信息。

在Haskell年轻时,Lennart Augustsson写了另一种方法 在备忘录使用系统令牌的功能I / O 中。后来他与 Mikael Rittri和Dan Synek在功能性珍珠上创造独特 名称,它描述了另一种方法-定义数据结构 当访问其节点时发生副作用。然后,以效果为中心的定义接收一个节点作为额外参数,可以直接使用该节点,也可以提供子节点来调用其他此类定义。

与状态传递和相关方法不同,定义不必 返回带有或结果的修改后的节点。与单调方法不同, 改进了其他定义以使用基于效果的结果所需的工作(例如,lifting):

我赞扬了单子,让我提出一个批评。 Monad往往是一个全有或全无的主张。如果发现需要在程序的深处进行交互,则必须重写该段以使用monad。 [...]

(摘自Wadler论文的第29页。)

John Launchbury和Simon Peyton Jones可以在early example中找到一种State in Haskell的Augustsson-Rittri-Synek方法。