gpt4 book ai didi

haskell - 除了 Monads 之外,还有哪些其他方式可以在纯函数式语言中处理状态?

转载 作者:行者123 更新时间:2023-12-03 06:20:31 25 4
gpt4 key购买 nike

所以我开始关注 Monads(在 Haskell 中使用)。我很好奇在纯函数式语言(无论是理论上还是现实中)中还有哪些其他方式可以处理 IO 或状态。例如,有一种称为“mercury”的逻辑语言使用“效果类型”。在诸如haskell 之类的程序中,效果类型如何工作?其他系统如何工作?

最佳答案

这里涉及几个不同的问题。

一、IOState是非常不同的事情。 State很容易做到
你自己:只需向每个函数传递一个额外的参数,并返回一个额外的
结果,你有一个“有状态的功能”;例如,转 a -> b进入a -> s -> (b,s) .

这里不涉及魔法:Control.Monad.State提供了一个包装器
使用形式为 s -> (a,s) 的“状态 Action ”方便,也
作为一堆辅助函数,仅此而已。

I/O 就其本质而言,在其实现中必须具有某种魔力。但这有
很多在 Haskell 中表达 I/O 的方法都不涉及“monad”这个词。
如果我们有一个 Haskell 的无 IO 子集,并且我们想从
从头开始,在对 monad 一无所知的情况下,我们可能会做很多事情
做。

例如,如果我们只想打印到标准输出,我们可能会说:

type PrintOnlyIO = String

main :: PrintOnlyIO
main = "Hello world!"

然后有一个 RTS(运行时系统)来评估字符串并打印它。
这让我们可以编写任何 I/O 完全由打印组成的 Haskell 程序
到标准输出。

然而,这不是很有用,因为我们想要交互性!所以让我们发明
一种允许它的新型 IO。想到的最简单的事情是
type InteractIO = String -> String

main :: InteractIO
main = map toUpper

这种 IO 方法让我们可以编写任何从 stdin 读取并写入的代码
stdout(Prelude 带有一个函数 interact :: InteractIO -> IO ()顺便说一下,这是这样做的)。

这要好得多,因为它让我们可以编写交互式程序。但它
与我们想做的所有 IO 相比仍然非常有限,而且也相当
容易出错(如果我们不小心尝试读取太远的 stdin,程序
只会阻塞,直到用户输入更多)。

我们希望能够做的不仅仅是读取标准输入和写入标准输出。就是这样
Haskell 的早期版本做了 I/O,大约:
data Request = PutStrLn String | GetLine | Exit | ...
data Response = Success | Str String | ...
type DialogueIO = [Response] -> [Request]

main :: DialogueIO
main resps1 =
PutStrLn "what's your name?"
: GetLine
: case resps1 of
Success : Str name : resps2 ->
PutStrLn ("hi " ++ name ++ "!")
: Exit

当我们写 main ,我们得到一个惰性列表参数并返回一个惰性列表作为
结果。我们返回的惰性列表具有类似 PutStrLn s 的值和 GetLine ;
在我们产生一个(请求)值之后,我们可以检查下一个元素
(响应)列表,RTS 将安排它作为对我们的响应
要求。

有很多方法可以让这个机制更好地工作,但你可以
想象一下,这种方法很快就会变得非常尴尬。还有就是
和上一个一样容易出错。

这是另一种更不容易出错的方法,并且在概念上非常
接近 Haskell IO 的实际行为方式:
data ContIO = Exit | PutStrLn String ContIO | GetLine (String -> ContIO) | ...

main :: ContIO
main =
PutStrLn "what's your name?" $
GetLine $ \name ->
PutStrLn ("hi " ++ name ++ "!") $
Exit

关键是,不要将响应的“懒惰列表”作为一个大的
在 main 开始时的参数,我们提出接受一个的个别请求
一次争论。

我们的程序现在只是一个普通的数据类型——很像一个链表,除了
你不能只是正常遍历它:当 RTS 解释 main , 有时
它遇到类似 GetLine 的值它拥有一个函数;那么它必须得到
使用 RTS 魔法从 stdin 获取字符串,并将该字符串传递给函数,
在它可以继续之前。练习:写 interpret :: ContIO -> IO () .

请注意,这些实现都没有涉及“世界传递”。
“世界传递”并不是 Haskell 中 I/O 的真正工作方式。实际上 IO 的实现GHC 中的类型涉及一个内部类型,称为 RealWorld ,但这只是一个实现细节。

实际 Haskell IO添加一个类型参数,以便我们可以编写操作
“产生”任意值——所以它看起来更像是 data IO a = Done a |
PutStr String (IO a) | GetLine (String -> IO a) | ...
.这给了我们更多
灵活性,因为我们可以创建“ IO Action ”,产生任意
值。

(如罗素·奥康纳 points out
这种类型只是一个免费的 monad。我们可以写一个 Monad例如很容易。)

那么 monad 是从哪里来的呢?原来我们不需要 Monad为了
I/O,我们不需要 Monad对于状态,那么我们为什么需要它呢?这
答案是我们没有。类型类没有什么神奇之处 Monad .

但是,当我们使用 IO 时和 State (以及列表和函数以及 Maybe和解析器和继续传递风格和...)足够长的时间,我们
最终发现他们在某些方面的行为非常相似。我们可能
编写一个打印列表中每个字符串的函数,以及一个运行的函数
列表中的每个有状态计算并通过线程处理状态,它们将
看起来彼此非常相似。

因为我们不喜欢写很多看起来相似的代码,所以我们想要一种方法
抽象它; Monad事实证明这是一个很好的抽象,因为它让我们
抽象了许多看起来很不一样的类型,但仍然提供了很多有用的
功能(包括 Control.Monad 中的所有内容)。

给定 bindIO :: IO a -> (a -> IO b) -> IO breturnIO :: a -> IO a , 我们
可以写任何 IO在 Haskell 中编程,而无需考虑 monad。但
我们可能最终会复制 Control.Monad 中的许多功能,
喜欢 mapMforeverwhen(>=>) .

通过实现通用 Monad API,我们可以使用完全相同的代码
像处理解析器和列表一样处理 IO 操作。这真的是唯一
我们拥有 Monad 的原因类——捕捉之间的相似之处
不同种类。

关于haskell - 除了 Monads 之外,还有哪些其他方式可以在纯函数式语言中处理状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13536761/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com