功能语言中的异步编程


31

我主要是C / C ++程序员,这意味着我的大部分经验都涉及过程和面向对象的范例。但是,正如许多C ++程序员所知道的那样,多年来,C ++的重点已转移到具有功能的风格,最终在C ++ 0x中增加了lambda和闭包。

无论如何,尽管我在使用C ++ 进行功能样式编码方面有丰富的经验,但是我对诸如Lisp,Haskell等实际功能语言的经验却很少。

我最近开始研究这些语言,因为纯功能语言中的“无副作用”的想法一直吸引着我,特别是在将其应用于并发和分布式计算方面。

但是,出于C ++的背景,我对于这种“无副作用”的哲学与异步编程的工作方式感到困惑。异步编程是指可以分发用户提供的事件处理程序以处理异步发生的事件(程序流之外)的任何框架/ API /编码样式。这包括异步库,例如Boost.ASIO,甚至只是普通的C语言。信号处理程序或Java GUI事件处理程序。

所有这些共同点是,异步编程的性质似乎需要创建副作用(状态),以使程序的主要流程意识到已调用了异步事件处理程序。通常,在Boost.ASIO之类的框架中,事件处理程序会更改对象的状态,从而使事件的影响传播到事件处理程序功能的生存期之外。真的,事件处理程序还能做什么?它不能“返回”值到调用点,因为没有调用点。事件处理程序不是程序主流程的一部分,因此它对实际程序有任何影响的唯一方法是更改​​某些状态(或更改longjmp为另一个执行点)。

因此,似乎异步编程是关于异步产生副作用的。这似乎与函数式编程的目标完全矛盾。这两种范例在功能语言中如何进行协调(实际上)?


3
哇,我正要写一个像这样的问题,却不知道如何表达,然后在建议中看到了!
Amogh Talpallikar

Answers:


11

您的所有逻辑都是合理的,除了我认为您对函数式编程的理解有点太极端了。在现实世界中,函数式编程就像面向对象或命令式编程一样,都是关于心态以及如何解决问题的。您仍然可以在修改应用程序状态时本着函数式编程的精神编写程序。

实际上,您必须修改应用程序状态才能实际执行任何操作。Haskell家伙会告诉您他们的程序是“纯”的,因为他们将所有状态更改都包装在monad中。但是,他们的程序仍然可以与外界交互。(否则有什么意义!)

函数式编程在有意义时会强调“无副作用”。但是,如您所说,要进行现实世界的编程,您确实需要修改世界的状态。(例如,响应事件,写入磁盘等。)

有关使用功能语言进行异步编程的更多信息,我强烈建议您研究F#的“异步工作流”编程模型。它允许您编写函数程序,同时在库中隐藏线程转换的所有混乱细节。(在某种程度上类似于Haskell风格的monad。)

如果线程的“主体”只是计算一个值,则产生多个线程并使它们并行计算值仍在功能范式之内。


5
另外:查看Erlang会有帮助。该语言非常简单,纯净(所有数据都是不可变的),并且都是关于异步处理的。
9000

基本上,在了解了功能性方法的好处并且仅在重要的时候才更改状态后,将自动确保即使您使用Java之类的语言,也可以知道何时修改状态以及如何控制这些状态。
Amogh Talpallikar,2013年

不同意-程序由“纯”函数组成的事实并不意味着它不会与外界交互,而是意味着程序中具有一组参数的每个函数将始终返回相同的结果,这是(纯度)很大,因为从实际的角度来看-这样的程序将不会出现更多的错误,而是更具“可测试性”,可以用数学方式证明函数的成功执行。
Gill Bates

8

这是一个有趣的问题。在我看来,最有趣的是Clojure中采用的方法,并在此视频中进行了解释:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

提出的“解决方案”基本上如下:

  • 您将大多数代码编写为具有不变数据结构且无副作用的经典“纯”函数
  • 副作用是通过使用托管引用来隔离的,这些引用可根据软件事务存储规则来控制更改(即,对可变状态的所有更新都在适当的隔离事务中进行)
  • 如果从世界的角度来看,您会看到异步“事件”是可变状态事务更新的触发器,而更新本身就是一个纯函数。

我可能没有像其他人那样清楚地表达这个想法,但是我希望这能给出总体想法-基本上,它是使用并发STM系统提供纯函数编程和异步事件处理之间的“桥梁”。


6

需要注意的是:一种功能语言是纯净的,但它的运行时并非如此。

例如,Haskell运行时涉及队列,线程多路复用,垃圾回收等……所有这些都不是纯粹的。

懒惰就是一个很好的例子。Haskell支持惰性评估(实际上是默认值)。您可以通过准备一个操作来创建一个惰性值,然后可以创建该值的多个副本,只要不需要它,它仍然是“惰性”的。当需要结果时,或者如果运行时找到了某个时间,则实际上将计算该值,并且惰性对象的状态将更改为反映不再需要执行计算(一次以上)来获取其结果。现在可以通过所有引用使用它,因此对象的状态已更改,即使它是纯语言也是如此。


2

对于这种“无副作用”的哲学如何与异步编程一起使用,我感到困惑。异步编程是指...

那就是重点。

合理的,没有副作用的样式与依赖状态的框架不兼容。寻找一个新的框架。

例如,Python的WSGI标准允许我们构建没有副作用的应用程序。

这个想法是,各种“状态更改”都可以通过可逐步构建的值环境来反映。每个请求都是转换的管道。


“允许不构建任何副作用的应用程序”,我认为某个地方缺少一个词。
Christopher Mahan

1

在学习了C语言之后,从Borland C ++学习了封装,当Borland C ++缺少启用泛型的模板时,面向对象范例使我感到不安。一种更自然的计算方式似乎是通过管道过滤数据。向外的流与向内的不可变输入流具有独立的标识,而不是被认为是副作用,即每个数据源(或过滤器)是相互独立的。按键(示例事件)将异步用户输入组合限制为可用的按键代码。函数对输入参数自变量进行操作,而类封装的状态只是避免在函数的小子集之间显式传递重复自变量的捷径,除了要注意绑定上下文,防止从任何任意函数中滥用这些自变量。

严格遵守特定范式会导致处理例如抽象的不便之处。商业运行时(例如JRE,DirectX,.net)最主要面向对象的支持者。为了限制这种不便,语言要么选择了像Haskell那样的学术精巧的Monad,要么最终选择了F#等灵活的多范式支持。除非封装在某些多重继承用例中有用,否则多范例方法可能是某些(有时是复杂的,特定于范例的)编程模式的替代方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.