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

Gol*_*den 14 functional-programming purely-functional

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

但有一点我没有得到:如何在限制自己使用纯函数时处理副作用.

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

var add = function (first, second) {
  return first + second;
};
Run Code Online (Sandbox Code Playgroud)

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

kqr*_*kqr 16

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

我可以想到两种方法.至少存在我忘记的第三个.


I/O流

最容易理解的方法可能是流式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')]
Run Code Online (Sandbox Code Playgroud)

并且相应的输入流将是

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

看看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
Run Code Online (Sandbox Code Playgroud)

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


这个"解释"接近于询问关于你不应该提出问题的神奇机器的问题,所以我将用一些智慧来包装它.

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

  • 千万不能尝试使用我的例子来了解如何与纯粹的I/O工作.引擎盖下的工作方式与使用它的方式完全不同.如果你以前从未见过汽车,那么通过阅读其中一个蓝图,你就不会成为一个好的驾驶员.

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

    这与通过查看反汇编的JVM字节码学习Java的方式类似.

  • 学会用一元的I/O和基于流的I/O.这是一种很酷的体验,在您的工具带下拥有更多工具总是好的.


Jon*_*oni 5

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

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