Haskell 除了monad之外,纯函数式语言还能用什么方式处理状态?

Haskell 除了monad之外,纯函数式语言还能用什么方式处理状态?,haskell,functional-programming,mercury,Haskell,Functional Programming,Mercury,所以我开始把我的头缠在单子上(用于Haskell)。我很好奇在纯函数式语言中(无论是在理论上还是在现实中)还有什么其他方式可以处理IO或状态。例如,有一种称为“mercury”的逻辑语言使用“效果类型”。在haskell这样的程序中,如何影响打字工作?其他系统是如何工作的 另一个主要方法是,如。简而言之,状态句柄(包括真实世界)只能使用一次,而访问可变状态的函数将返回一个新句柄。这意味着第一次调用的输出是第二次调用的输入,从而强制进行顺序求值 在for Haskell中使用了Effect类型,但

所以我开始把我的头缠在单子上(用于Haskell)。我很好奇在纯函数式语言中(无论是在理论上还是在现实中)还有什么其他方式可以处理IO或状态。例如,有一种称为“mercury”的逻辑语言使用“效果类型”。在haskell这样的程序中,如何影响打字工作?其他系统是如何工作的

另一个主要方法是,如。简而言之,状态句柄(包括真实世界)只能使用一次,而访问可变状态的函数将返回一个新句柄。这意味着第一次调用的输出是第二次调用的输入,从而强制进行顺序求值


在for Haskell中使用了Effect类型,但据我所知,在GHC中启用它需要大量的编译器工作。我将把细节的讨论留给那些比我更了解情况的人。

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

首先,
IO
State
是非常不同的事情<代码>状态很容易做到 你自己:只需给每个函数传递一个额外的参数,然后返回一个额外的参数 结果,并且您有一个“有状态函数”;例如,将
a->b
转换为
a->s->(b,s)

这里没有魔法:
Control.Monad.State
提供了一个 使使用
s->(a,s)
形式的“状态操作”也变得方便 作为一堆辅助函数,但仅此而已

从本质上讲,I/O的实现必须有一些魔力。但是有 在Haskell中有很多表示I/O的方法,它们不涉及“monad”这个词。 如果我们有一个Haskell的无IO子集,我们想从 scratch,在对monads一无所知的情况下,我们可能会做很多事情 做

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

type PrintOnlyIO = String

main :: PrintOnlyIO
main = "Hello world!"
然后有一个RTS(运行时系统)来计算字符串并打印它。 这让我们可以编写任何I/O完全由打印组成的Haskell程序 到stdout

但是,这不是很有用,因为我们需要交互性!所以让我们发明 一种新的IO类型,允许使用它。我想到的最简单的事情是

type InteractIO = String -> String

main :: InteractIO
main = map toUpper
这种IO方法允许我们编写任何从stdin读取并写入的代码 stdout(前奏曲带有一个函数
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
时,我们得到一个lazy list参数,并返回一个lazy list作为参数 结果。我们返回的延迟列表具有类似于
putstrlns
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
关键是不要把一个“懒散的列表”当作一个大列表 在主语的开头,我们单独提出请求,接受其中一个 一次争论一次

我们的程序现在只是一种常规的数据类型——非常类似于链表,除了 您不能正常地遍历它:当RTS解释
main
时,有时 它遇到一个像
GetLine
这样的值,该值保存一个函数;那它就得走了 使用RTS magic从stdin获取字符串,并将该字符串传递给函数, 然后才能继续。练习:编写
解释::ContIO->IO()

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

Actual Haskell
IO
添加了一个类型参数,这样我们就可以编写 “生成”任意值——因此它看起来更像
data IO a=Done a|
PutStr字符串(IO a)|获取行(字符串->IO a)|……
。这给了我们更多 灵活性,因为我们可以创建生成任意 价值观

(作为罗素·奥康纳, 这种类型只是一个免费的monad。我们可以很容易地为它编写一个
monad
实例。)


那么,单子是从哪里来的呢?事实证明,我们不需要Monad I/O,我们不需要状态的
Monad
,那么为什么我们需要它呢?这个 答案是我们没有。类型类
Monad
没有什么神奇之处

但是,当我们使用
IO
状态
(以及列表、函数和
可能
和解析器以及继续传递样式和…)足够长的时间,我们 最终发现它们在某些方面的行为非常相似。我们可能 编写一个打印列表中每个字符串的函数和一个运行的函数 列表中的每一个有状态计算都会执行该状态,它们将 看起来非常相似

因为我们不喜欢编写很多类似的代码,所以我们需要一种 抽象它
Monad
是一个很好的抽象,因为它让我们 抽象了许多看起来非常不同的类型,但仍然提供了许多有用的 功能(包括
控件中的所有内容
data Stream a = Stream a (Stream a)
stepStream :: Stream a -> (a, Stream a)
stepStream (Stream x xs) = (x, xs)
newtype Auto a b = Auto (a -> (b, Auto a b))
data LS a b =
    forall s.
    LS s ((a, s) -> (b, s))
main k = print "What is your name? " $
    getLine $ \ myName ->
    print ("Hello, " ++ myName ++ "\n") $
    k ()
main w0 = let
        w1 = print "What is your name? " w0
        (w2, myName) = getLine w1
        w3 = print $ "Hello, " ++ myName ++ "!\n"
    in w3
main resps = [
    PrintReq "What is your name? ",
    GetLineReq,
    PrintReq $ "Hello, " ++ myName ++ "!\n"
  ] where
    LineResp myName = resps !! 1
type Cont = [Response] -> [Request]
print :: String -> Cont -> Cont
print msg k resps = PrintReq msg : case resps of
    PrintResp () : resps1 -> k resps1
getLine :: (String -> Cont) -> Cont
getLine k resps = GetLineReq : case resps of
    GetLineResp msg : resps1 -> k msg resps1