在向Monad初学者解释像s 这样的概念时,我认为避免任何复杂的Haskell术语或任何类别理论都是有帮助的.我认为解释它的一个很好的方法是为这个函数建立一个动机,a -> m b如下所示Maybe:
data Maybe = Just a | Nothing
Run Code Online (Sandbox Code Playgroud)
这是全有或全无.但是,如果我们有一些功能f :: a -> Maybe b并且g :: b -> Maybe c我们想要一种很好的方法来组合它们呢?
andThen :: Maybe a -> (a -> Maybe b) -> Maybe b
andThen Nothing _ = Nothing
andThen (Just a) f = f a
comp :: Maybe Text
comp = f a `andThen` g
where f g a = etc...
Run Code Online (Sandbox Code Playgroud)
然后你可以进入说andThen可以为各种类型定义(最终形成monad类型类)......对我来说,一个引人注目的下一个例子就是IO.但是你会如何andThen为IO自己定义?这引出了我自己的问题......我的天真实现andThenIO会是这样的:
andThenIO :: IO a -> (a -> IO b) -> IO b
andThenIO io f = f (unsafePerformIO io)
Run Code Online (Sandbox Code Playgroud)
但我知道这不是你>>=使用时实际发生的事情IO.综观实行bindIO在GHC.Base我看到这一点:
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO (\ s -> case m s of (# new_s, a #) -> unIO (k a) new_s)
Run Code Online (Sandbox Code Playgroud)
而对于unIO这一点:
unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a
Run Code Online (Sandbox Code Playgroud)
这似乎与STmonad有某种关系,虽然我的知识几乎ST没有......我想我的问题是,我的天真实现与使用的实现之间究竟有什么区别ST?考虑到这个例子,我的天真实现是否有用,因为它实际上并没有在幕后进行(可能是误导性的解释)
chi*_*chi 10
(注:这回答了"如何解释如何IO工作的一个初学者的部分"它不试图解释.RealWorld#GHC使用的确黑客,后者不是引进一个好办法.IO)
有许多方法可以向初学者解释IO monad.这很难,因为不同的人在心理上将monad与不同的想法联系起来.您可以使用类别理论,或将它们描述为可编程分号,甚至可以将其描述为卷饼.
因此,当我过去尝试这样做时,我通常会尝试很多方法,直到其中一个方法"点击"进入学习者的心理模式.了解他们的背景有很大帮助.
例如,当学习者已经熟悉一些带闭包的命令式语言时,例如JavaScript,我倾向于告诉他们他们可以假装Haskell程序的重点是生成一个JavaScript闭包,然后使用JavaScript实现运行.在这个虚构的解释,一个IO T类型代表不透明型封装的JavaScript关闭,其中,在运行时,会产生类型的值T,可能引起一些副作用之后-如JavaScript可以做到.
因此,一个值f :: IO String可以实现为
let f = () => {
print("side effect");
return "result";
};
Run Code Online (Sandbox Code Playgroud)
并g :: IO ()可以实现为
let g = () => {
print("g here");
return {};
};
Run Code Online (Sandbox Code Playgroud)
现在,假设有这样的f闭包,如何从Haskell调用它?好吧,人们不能直接这样做,因为Haskell希望控制副作用.也就是说,我们不能做f ++ "hi"或f() ++ "hi".
相反,为了"调用一个闭包",我们可以将它绑定到 main
main :: IO ()
main = g
Run Code Online (Sandbox Code Playgroud)
实际上,main是由整个Haskell程序生成的JavaScript闭包,这将由Haskell实现调用.
好的,现在问题变成:"如何调用多个闭包?".为此,可以引入>>并假装它被实现为
function andThenSimple(f, g) {
return () => {
f();
return g();
};
}
Run Code Online (Sandbox Code Playgroud)
或者,用于>>=:
function andThen(f, g) {
return () => {
let x = f();
return g(x)(); // pass x, and then invoke the resulting closure
};
}
Run Code Online (Sandbox Code Playgroud)
return 更容易
function ret(x) {
return () => x;
}
Run Code Online (Sandbox Code Playgroud)
这些函数需要一段时间才能解释,但如果理解闭包,就不难理解它们.
另一种选择是保持一切纯净.或者至少尽可能纯净.可以假设这IO a是一个定义为的不透明类型
data IO a
= Return a
| Output String (IO a)
| Input (String -> IO a)
-- ... other IO operations here
Run Code Online (Sandbox Code Playgroud)
然后假装main :: IO ()某个命令式引擎随后"运行" 该值.像这样的程序
foo :: IO Int
foo = do
l <- getLine
putStrLn l
putStrLn l
return (length l)
Run Code Online (Sandbox Code Playgroud)
实际上,根据这种解释,
foo :: IO Int
foo = Input (\l -> Output l (Output l (Return (length l))))
Run Code Online (Sandbox Code Playgroud)
当然在这里return = Return,定义>>=是一个很好的练习.
忘记IO,monads和所有这些东西.人们可以理解更好的两个简单概念
a -> b -- pure function type
a ~> b -- impure function type
Run Code Online (Sandbox Code Playgroud)
后者是一种虚构的Haskell类型.大多数程序员应该能够对这些类型所代表的内容有很强的直觉.
现在,在函数式编程中,我们有currying,这是一个同构之间
(a, b) -> c
Run Code Online (Sandbox Code Playgroud)
和
a -> b -> c
Run Code Online (Sandbox Code Playgroud)
经过一番思考后,人们可以看到不纯的功能也应该承认一些曲解.人们确实可以确信应该有一些类似的同构
(a, b) ~> c
<===>
a ~> b ~> c
Run Code Online (Sandbox Code Playgroud)
对于一些更多的思考,人们甚至可以理解的是,第一~>中a ~> b ~> c其实是不准确的.当a单独传递时,上面的curried函数并不真正执行副作用- 它的传递b会触发原始未传递函数的执行,从而导致副作用.
因此,考虑到这一点,我们可以将currying视为
(a, b) ~> c
<===>
a -> b ~> c
--^^-- pure!
Run Code Online (Sandbox Code Playgroud)
作为一个特例,我们得到了同构
(a, ()) ~> c
<===>
a -> () ~> c
Run Code Online (Sandbox Code Playgroud)
此外,由于(a, ())是同形的a(这里需要更有说服力),我们可以将currying解释为
a ~> c
<===>
a -> () ~> c
Run Code Online (Sandbox Code Playgroud)
现在,如果我们施洗() ~> c的IO c,我们得到
a ~> c
<===>
a -> IO c
Run Code Online (Sandbox Code Playgroud)
啊,哈!这告诉我们,我们并不真正需要一般的不纯函数类型a ~> c.只要我们有其特殊情况IO c = () ~> c,我们就可以表示(直到同构)任何a ~> c函数.
从这里开始,人们可以开始画出一幅关于IO c应该如何工作的心理图画,并最终实现它的一元结构.从本质上讲,这种解释IO c现在非常类似于上面给出的利用闭包的解释.