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