函数式编程包括许多不同的技术。有些技术可以带来副作用。但是一个重要的方面是方程式推理:如果我用相同的值调用一个函数,我总是会得到相同的结果。因此,我可以将函数调用替换为返回值,并获得等效的行为。这使得对程序进行推理变得更容易,尤其是在调试时。
如果该功能有副作用,那将不成立。返回值不等于函数调用,因为返回值不包含副作用。
解决的办法是停止使用侧的效果和编码这些效果在返回值中。不同的语言具有不同的效果系统。例如,Haskell使用monad来编码某些效果,例如IO或状态突变。C / C ++ / Rust语言的类型系统可以禁止某些值的突变。
在命令式语言中,print("foo")
函数将打印某些内容,但不返回任何内容。在像Haskell这样的纯函数式语言中,print
函数还接受表示外部世界状态的对象,并在执行此输出后返回表示状态的新对象。类似的东西newState = print "foo" oldState
。我可以根据需要从旧状态创建尽可能多的新状态。但是,主功能只能使用一个。因此,我需要通过链接功能对多个动作的状态进行排序。要打印foo bar
,我可能会说类似print "bar" (print "foo" originalState)
。
如果未使用输出状态,则Haskell不会执行导致该状态的操作,因为它是一种惰性语言。相反,这种懒惰是可能的,因为所有效果都被明确编码为返回值。
请注意,Haskell是使用此路由的唯一常用功能语言。其他功能语言,包括 Lisp家族,ML家族和诸如Scala之类的新功能语言不鼓励使用,但仍会产生副作用-它们可以称为命令性功能语言。
为I / O使用副作用可能很好。通常,I / O(除了日志记录)仅在系统的外部边界进行。您的业务逻辑中不会发生任何外部通信。这样就可以以纯风格编写软件的核心,同时仍在外壳中执行不纯的I / O。这也意味着核心可以是无状态的。
无状态具有许多实际优势,例如增加了合理性和可伸缩性。这对于Web应用程序后端非常流行。任何状态都保留在共享数据库的外部。这使负载平衡变得容易:我不必将会话停留在特定的服务器上。如果我需要更多服务器怎么办?只需添加另一个,因为它使用的是同一数据库。如果一台服务器崩溃怎么办?我可以在另一台服务器上重做任何待处理的请求。当然,数据库中仍然存在状态。但是我已经将其明确并提取出来,并且如果需要,可以在内部使用纯函数方法。