函数式语言如何模拟副作用?

发布于 2024-09-25 21:37:15 字数 40 浏览 8 评论 0原文

既然副作用破坏了引用透明性,那么它们是否违背了函数式语言的观点呢?

Since side-effects break referential transparency, don't they go against the point of functional languages?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

寄意 2024-10-02 21:37:15

纯函数式编程语言使用两种技术来模拟副作用:

1) 表示外部状态的世界类型,其中类型系统保证该类型的每个值仅使用一次。

在使用这种方法的语言中,函数 printread 可能具有类型 (string, world) ->世界世界-> (string, world) 分别。

它们可以这样使用:

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w1 in
  let w3 = print ("Your name is " ^ name, w2) in
  w3

但不是这样:(

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w in
  let w3 = print ("Your name is " ^ name, w2) in
  w3

因为 w 使用了两次)

所有具有副作用的内置函数都将获取并返回一个世界值。由于所有具有副作用的函数要么是内置函数,要么调用其他具有副作用的函数,这意味着所有具有副作用的函数都需要获取并返回一个世界。

这样,就不可能使用相同的参数两次调用具有副作用的函数,并且不会违反引用透明度。

2) IO monad,其中所有具有副作用的操作都必须在该 monad 内执行。

通过这种方法,所有具有副作用的操作都将具有类型io some。例如 print 将是一个类型为 string ->; 的函数。 io unitread 的类型为 io string

访问执行操作的值的唯一方法是使用“一元绑定”操作(例如,在 haskell 中称为 >>=),将 IO 操作作为一个参数,并使用一个描述如何处理结果的函数,如下所示另一个操作数。

上面的例子对于 Monadic IO 来说是这样的:

let main =
  (print "What's your name?") >>=
  (lambda () -> read >>=
  (lambda name -> print ("Your name is " ^ name)))

There are two techniques that are used by purely functional programming languages to model side effects:

1) A world type that represents external state, where each value of that type is guaranteed by the type system to be used only once.

In a language that uses this approach the function print and read might have the types (string, world) -> world and world -> (string, world) respectively.

They might be used like this:

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w1 in
  let w3 = print ("Your name is " ^ name, w2) in
  w3

But not like this:

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w in
  let w3 = print ("Your name is " ^ name, w2) in
  w3

(because w is used twice)

All built-in functions with side-effects would take and return a world value. Since all functions with side-effects are either built-ins or call other functions with side-effects, this means that all functions with side-effects need to take and return a world.

This way it is not possible to call a function with side-effects twice with the same arguments and referential transparency is not violated.

2) An IO monad where all operations with side effects have to be executed inside that monad.

With this approach all operations with side effects would have type io something. For example print would be a function with type string -> io unit and read would have type io string.

The only way to access the value of performing operation would be to use the "monadic bind" operation (called >>= in haskell for example) with the IO operation as one argument and a function describing what to do with the result as the other operand.

The example from above would look like this with monadic IO:

let main =
  (print "What's your name?") >>=
  (lambda () -> read >>=
  (lambda name -> print ("Your name is " ^ name)))
绅刃 2024-10-02 21:37:15

有多种选项可用于以函数式语言处理 I/O。

  • 别太纯洁了许多函数式语言并不是纯粹的函数式语言。更重要的是他们支持
    函数式编程而不是强制执行。这是迄今为止最常见的问题解决方案
    函数式编程中的 I/O。 (示例:Lisp、Scheme、Standard ML、Erlang 等)
  • 流转换。早期的 Haskell I/O 就是这样完成的。如果您有的话,请查看我下面的链接了解详细信息
    想要更多信息。 (提示:您可能不知道。)
  • 连续传递 I/O(其他答案中提到的“世界传递”)。在这个中你传递一个令牌
    I/O 周围的数据充当保持引用完整性所需的“不同值”
    活。如果没记错的话,多种 ML 方言都会使用它。
  • 上面的“延续”或“世界”事物可以包装在各种数据类型中,其中最著名的是
    在 Haskell 中使用 monad 来扮演这个角色。请注意,理论上,这与
    封面,但是删除了跟踪“世界”/“延续”状态变量的乏味。

一篇研究论文详尽地分析了这些。

函数式 I/O 是一个持续的研究领域,还有其他语言以有趣且令人费解的方式解决这个问题。 霍尔逻辑在某些研究语言中使用。其他(例如 Mercury)使用 唯一性输入。还有一些(例如 Clean)使用 效果系统。其中我对水星的接触非常非常有限,所以我无法对细节发表评论。有一个 不过,如果您对这个方向感兴趣,请参阅论文,深入详细介绍 Clean 的 I/O 系统。

There are several options available to handle I/O in a functional language.

  • Don't be pure. Many functional languages aren't purely functional. It's more that they support
    functional programming rather than enforcing it. This is by far the most common solution to the problem
    of I/O in functional programming. (Examples: Lisp, Scheme, Standard ML, Erlang, etc.)
  • Stream transformation. Early Haskell I/O was done this way. Check my link below for details if you
    want more information. (Hint: you probably don't.)
  • Continuation-passing I/O (the "world-passing" mentioned in other answers). In this one you pass a token
    of data around with your I/O that acts as the necessary "different value" to keep referential integrity
    alive. This is used by several ML dialects if memory serves.
  • The "continuation" or "world" thing above can be wrapped in various data types, the most (in)famous
    being the use of monads in this role in Haskell. Note that this is, notionally, the same thing under
    the covers, but the tedium of keeping track of "world"/"continuation" state variables is removed.

There's a research dissertation that exhaustively analyses these.

Functional I/O is an ongoing field of research and there are other languages which address this issue in interesting and mind-mangling ways. Hoare logic is put to use in some research languages. Others (like Mercury) use uniqueness typing. Still others (like Clean) use effect systems. Of these I have a very, very limited exposure to Mercury only, so I can't really comment on details. There's a paper that details Clean's I/O system in depth, however, if you're interested in that direction.

笑脸一如从前 2024-10-02 21:37:15

据我所知,如果你想在函数式语言中产生副作用,你必须显式地对其进行编码。

To the best of my understanding, if you want to have side effects in a functional language, you have to code them explicitly.

梨涡 2024-10-02 21:37:15

既然副作用破坏了引用透明性,那么它们是否违背了函数式语言的要点?

这取决于函数式语言:

  • 标准机器学习允许自由使用副作用像大多数过程语言一样,例如 Fortran、Algol、Pascal、C 等。

  • 来限制副作用< /em> 类似于 IOSTSTM,这有助于保持引用透明度。

  • Clean 也限制了副作用,但这是通过其扩展类型系统实现的。< /p>

  • Coq 使用的函数式语言 - Gallina -提供根本无法获得副作用


函数式语言如何模拟副作用?

一种不经常提及的方法依赖于伪数据:在可访问的结构化值(通常是树)中传达单独的一次性抽象值,仅当每个抽象值被最初使用。有关更多信息,请参阅 F. Warren Burton 的 非决定论函数式编程语言中的引用透明度。还可以在 GHC 中找到一个工作示例:其 独特的名称提供类型。

但是,如果使伪数据工作所需的额外参数太烦人,那么实际上可以将 I/O 及其可观察的效果与非严格语义有效地结合起来......如果您并不真正需要引用透明度。

Since side-effects break referential transparency, don't they go against the point of functional languages?

It depends on the functional language:

  • Standard ML allows the liberal use of side-effects like most procedural languages e.g. Fortran, Algol, Pascal, C, etc.

  • Haskell restricts side-effects through the use of abstract data types like IO, ST and STM, which helps to preserve referential transparency.

  • Clean also restricts side-effects, but does this with its extended type system.

  • The functional language Coq uses - Gallina - provides no access to side-effects at all.


How do functional languages model side-effects?

One approach which isn't regularly mentioned relies on pseudo-data: individual single-use abstract values conveyed in an accessible structured value (commonly a tree), with the side effects only occuring when each abstract value is initially used. For more information, see F. Warren Burton's Nondeterminism with Referential Transparency in Functional Programming Language. An working example can also be found in GHC: its Unique name-supply type.

But if the extra parameters needed to make pseudo-data work is just too annoying, it is actually possible to usefully combine I/O and its observable effects with non-strict semantics...if you don't really need referential transparency.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文