纯函数式编程中是否存在副作用

Jer*_*y E 17 functional-programming side-effects

我一直试图将功能编程包围一段时间?我已经查找了lambda演算,LISP,OCML,F#甚至组合逻辑,但我遇到的主要问题是你如何做需要副作用的事情(与用户交互,与远程服务通信,甚至处理模拟使用)随机抽样)不违反纯函数式编程的基本前提,即对于给定的输入,输出是确定性的?我希望我有意义,如果不是,我欢迎任何正确教育我的尝试.提前致谢.

C. *_*ann 20

大多数现实世界的函数式编程在大多数情况下都不是"纯粹的",所以你的问题的答案的一半是"你通过放弃纯度来实现".这就是说,有替代品.

在"最纯粹"的纯粹意义上,整个程序代表一个或多个参数的单个函数,返回一个值.如果你眯着眼睛挥手,你可以声明所有用户输入都是函数的"参数"的一部分,并且所有输出都是"返回值"的一部分,然后稍微捏一下,这样它只会实际的I/O"按需".

类似的观点是声明函数的输入是"外部世界的整个状态",并且评估函数返回一个新的,修改的"世界状态".在这种情况下,程序中使用世界状态的任何功能显然都不会被"确定性"释放,因为对程序的两次评估不会具有完全相同的外部世界.

如果你想用纯lambda演算(或类似的东西,比如深奥的语言Lazy K)编写一个交互式程序,那么从概念上讲你是如何做到的.

在更实际的术语中,问题归结为确保当输入被用作函数的参数时I/O以正确的顺序出现.该问题的"纯"解决方案的一般结构是功能组合.例如,假设您有三个执行I/O的功能,并且您希望按特定顺序调用它们.如果你这样做RunThreeFunctions(f1, f2, f3),没有什么,以确定他们会在被评估的顺序.另一方面,如果你让每个函数取另一个函数作为参数,你可以把它们连这样的:f1( f2( f3()))在这种情况下,你知道f3的意志首先要评估,因为评估f2取决于其价值.[ 编辑:另请参阅下面关于懒惰与急切评估的评论.这很重要,因为在非常纯粹的语境中,懒惰的评估实际上很常见; 例如,纯粹的lambda演算中递归的标准实现在急切评估下是无终止的.

再次,要在lambda演算中编写一个交互式程序,这就是你可能会这样做的.如果你想要一些实际可用于编程的东西,你可能想要将函数组合部分与函数的概念结构结合起来并返回表示世界状态的值,并创建一些高阶抽象来处理流水线操作"世界状态"I/O函数之间的值,理想情况下也保持"世界状态",以强制执行严格的线性 - 此时你几乎彻底改造了Haskell的IOMonad.

希望这不仅会让你更加困惑.

  • 你当然是正确的,我为了不加剧答案已经非常庞大的冗长而掩盖了许多其他细节. (5认同)
  • "另一方面,如果你让每个函数都将另一个函数作为一个参数,你可以像这样链接它们:f1(f2(f3())),在这种情况下,你知道f3将首先被评估,因为评估f2取决于它的价值." - 只有当`f2`和`f1`实际上使用它的参数来计算其结果时,并且只有当`f1`的调用者也将使用其结果时.否则,懒惰的评估可以合法地进行. (4认同)
  • 为了迂腐,国际象棋比赛的世界状态还包括轮到它和某些棋子的部分运动历史(由于*en passant*捕捉和投掷的规则).Haskell风格的基于状态的monad封装了该状态的细节,并抽象了在函数之间传递状态数据的过程.理论上你可以传递一个显式的状态结构来获得相同的效果 - monad只是为了强制执行正确性并避免使用样板代码. (2认同)

R. *_*des 9

Haskell是一种纯函数式编程语言.在Haskell中,所有函数都是纯函数(即它们总是为相同的输入提供相同的输出).但是你如何处理Haskell中的副作用?嗯,这个问题通过monad的使用得到了很好的解决.

以I/O为例.在Haskell中,每个执行I/O的函数都返回IO计算,即IO monad中的计算.因此,例如,从键盘读取int而不是返回int的函数返回IO计算,该计算在运行时产生int:

askForInt :: String -> IO Int
Run Code Online (Sandbox Code Playgroud)

因为它返回I/O计算而不是a Int,所以不能直接在sum中使用此结果.为了访问Int您需要"解包"计算的值.唯一的方法是使用bind函数(>>=):

(>>=) :: IO a -> (a -> IO b) -> IO b
Run Code Online (Sandbox Code Playgroud)

因为这也会返回IO计算,所以您总是会进行I/O计算.这就是Haskell如何隔离副作用.IO monad充​​当现实世界状态的抽象(事实上,它通常用一个以RealWorld状态部分命名的类型实现).

  • Haskell很纯粹.Haskell程序没有副作用 - 它们返回一个_computation_(而不是一个值),给定一个"world"状态(包含在`IO`monad中),产生另一个"world"状态(返回` IO` monad). (4认同)
  • @RBarryYoung:你是什么意思,他们是"无功能"的?是什么让你说Haskell不是纯粹的功能?Haskell*纯粹是功能性的. (2认同)
  • 作为一种纯粹的功能语言并不意味着一切都是一种功能.它是一个**函数**语言,所有函数都是**纯**(从来没有'unsafePerformIO`) (2认同)

Lau*_*ves 7

与用户交互并与远程服务通信确实需要某种非功能部件到您的软件.

许多"函数式语言"(像大多数Lisps一样)并不是纯粹的功能.他们仍然让你做有副作用的事情,尽管在大多数情况下,副作用的事情都"沮丧".

Haskell"纯粹是功能性的",但仍然允许你通过IO monad做非功能性的东西.基本思想是你的纯功能程序发出一个懒惰的数据结构,由一个非功能性程序(你不写,它是环境的一部分)进行评估.有人可能会争辩说,这种数据结构本身就是一项势在必行的计划.所以你有点在功能语言中进行必要的元编程.

忽略哪种方法"更好",两种情况下的目标是在程序的功能部分和非功能部分之间建立分离,并尽可能地限制非功能部分的大小.功能部件往往更易于重复使用,可测试且易于推理.


jld*_*ont 5

功能编程是关于限制和隔离副作用,而不是试图完全摆脱它们......因为你不能.

......是的,我发现FP很有用(当然还有Erlang):我发现从"想法"到"程序"(或问题到解决方案)更容易......但当然可能只是我.


Jon*_*off 5

我所知道的唯一完全纯函数式语言是 C++ 中的模板系统。Haskell 通过使程序的命令部分变得明确而获得第二名。

在 Haskell 中,程序具有可变状态,但函数(几乎总是)没有。你保持了 99% 的程序纯净,只有与外界交互的部分是不纯净的。因此,当您测试某个功能时,您知道没有副作用。纯净的核心,带有不纯净的外壳。