print 2 :: IO ()
这个表达式有副作用 (side effect) 么?没有。就是这么简单。无论在哪里,print 2
都是指的“输出 2 这个数”。
但是这个不叫副作用叫什么?我们不妨把它叫做“作用 (effect)”。一个 IO a
描述了一个和 a
相关的作用。
print 2 = 输出 2
getLine = 输入 x,返回 x
这么说起来好像怪怪的,咱还是想想别的办法。
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
这里的 unboxed tuple (# a, b #)
类似于元组 (a, b)
,不必太在意。
现实世界有几个?
只有一个。
GHC 把现实世界封装起来,使它只有一个。这样,State# RealWorld
有如下性质:
x = IO f :: IO a
和 rw :: State# RealWorld
,当调用 f rw
后,rw
就相当于送给 f
了,你就不能再去碰它了。不过你可以用它返回的那个新的 State# RealWorld
。对于 x = IO f :: IO a
,这里的 f 可以包含任意的副作用,但是只要你好好地用封装好的 print
和 instance Monad IO
之类的东西的话,它也是引用透明 (referential transparent) 的。
为什么?因为
State# RealWorld
只有一个,所以你不能用相同的参数调用它,所以这个性质成立。State# RealWorld
只有一个,你不能用不同的顺序计算两个调用,所以这个性质成立。仔细想想这两件事。
那么,我们就可以说
IO f = print 2, f 接受一个世界 rw,返回 (# (stdout 多出了一行 2 的世界 rw'), () #)
IO f = getLine,f 接受一个世界 rw,返回 (# (stdin 里面被读了一行的世界 rw'), (读的那行) #)
但是在 IO f :: IO a
的执行过程当中,可能还会有别的变化,对吧?比如我敲了一行字,sxysxy 吃了一口饭之类的。这个怎么办?
我们把它当成 f
的副作用一部分就行了。
IO a
没有副作用State# RealWorld -> (# State# RealWorld, a #)
有副作用,不过管他呢,不影响引用透明。instance Monad IO
来看看 GHC 的封装是否真的符合前面说的性质
instance Monad IO where
return x = IO (\ s -> (# s, x #))
当然,送来的 State# RealWorld
可以原样奉还。
IO m >>= k = IO (\ s -> case m s of (# new_s, a #) -> unIO (k a) new_s)
s
没有了,我们把 new_s
送回去。
看,这样封装的不错吧?
State# RealWorld
的真面目说了这么多,真实世界是什么?
没有。
[
State#
] is represented by nothing at all. (Hackage)
直接去改这个世界就行了,不用去记下什么。
前面遇到的 unboxed tuple 也是类似的效果。(# a, b #)
在内存里的表示真的就是 a 和一个 b,没有任何更多的装饰。这样 (# State# RealWorld, a #)
在内存里的表示就是 a。
用 C 写出来,就是:
a something(void);