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

时间:2009-12-16 18:33:35

标签: functional-programming side-effects

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

9 个答案:

答案 0 :(得分:20)

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

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

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

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

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

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

希望这不会让你甚至更多感到困惑。

答案 1 :(得分:9)

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

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

askForInt :: String -> IO Int

因为它返回I / O计算而不是Int,所以不能直接在总和中使用此结果。要访问Int值,您需要“解包”计算。唯一的方法是使用绑定函数(>>=):

(>>=) :: IO a -> (a -> IO b) -> IO b

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

答案 2 :(得分:7)

与用户交互并与远程服务进行通信确实需要某种非功能性部件。

许多“函数式语言”(像大多数Lisps一样)不是纯粹的功能。他们仍然让你做有副作用的事情,虽然在大多数情况下,副作用的事情都“灰心丧气”。

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

忽略哪种方法“更好”,两种情况下的目标都是在程序的功能部分和非功能部分之间建立分离,并尽可能地限制非功能部分的大小。功能部件往往更具可重用性,可测试性,更容易推理。

答案 3 :(得分:5)

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

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

答案 4 :(得分:2)

您至少需要知道另一个基本概念:Monads。你需要这个来做I / O和其他“有用”的东西!

答案 5 :(得分:2)

Haskell的做法是使用monads看wikipedia和Haskell对page的解释。

基本上这个想法是你没有摆脱IO monad。我的理解是,您可以链接打开IO monad并执行该功能的函数。但是你无法完全删除IO monad。

另一个使用与IO没有直接关联的monad的例子是Maybe Monad。与IO monad相反,这个monad是“无法解决的”。但是使用Maybe monad更容易解释使用monad。我们假设你有以下功能。

wrap :: Maybe x -> (x -> y) -> Maybe y
wrap Nothing  f = Nothing
wrap (Just x) f = Just (f x)

现在您可以致电wrap (Just 4) (5+),它将返回Just 9

IO-monad的想法是你可以在内部类型上使用像(+5)这样的函数。 monad将确保函数将被串行调用,因为每个函数都与包装IO-monad链接在一起。

答案 6 :(得分:2)

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

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

答案 7 :(得分:1)

鉴于大多数程序对外界都有一些影响(写入文件,修改数据库中的数据......)整个程序很少是无副作用的。在学术练习之外,即使尝试也没有意义。

但是程序是由构建块组成的(子程序,函数,方法,可以根据需要调用它),纯函数可以构建非常好的构建块。

大多数函数式编程语言都不要求函数是纯粹的,尽管优秀的函数式程序员会尽可能地使其功能尽可能纯粹,以获得引用透明性的好处。

Haskell走得更远。 Haskell程序的每个部分都是纯粹的(至少在没有诸如“unsafePerformIO”之类的错误的情况下)。你在Haskell中编写的所有函数都是纯粹的。

通过monads引入副作用。它们可以用来引入一种“购物清单 - 购物者” - 分离。从本质上讲,您的程序会编写一个购物清单(这只是数据并且可以以纯粹的方式进行操作),而语言运行时会解释购物清单并进行有效的购物。所有代码都是纯粹的,对于等式推理是友好的,而不纯的代码则由编译器编写者提供。

答案 8 :(得分:0)

即使您不在工作中使用它,学习一种或多种函数式编程语言也是学习不同思考方式的好方法,并为您提供了一种替代问题解决方案的工具包(它可能会让您感到沮丧)在其他语言中做一些整洁干净的功能性方法。

这使我在编写XSL样式表方面做得更好。