dramforever

a row of my life

有关 monad 的一些想法 (2)

2016-12-26

没有副作用

print 2 :: IO ()

这个表达式有副作用 (side effect) 么?没有。就是这么简单。无论在哪里,print 2 都是指的“输出 2 这个数”。

但是这个不叫副作用叫什么?我们不妨把它叫做“作用 (effect)”。一个 IO a 描述了一个和 a 相关的作用。

这么说起来好像怪怪的,咱还是想想别的办法。

IO

newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

这里的 unboxed tuple (# a, b #) 类似于元组 (a, b),不必太在意。

现实世界有几个?

只有一个。

GHC 把现实世界封装起来,使它只有一个。这样,State# RealWorld 有如下性质:

对于 x = IO f :: IO a,这里的 f 可以包含任意的副作用,但是只要你好好地用封装好的 printinstance Monad IO 之类的东西的话,它也是引用透明 (referential transparent) 的。

为什么?因为

  1. 用相同的参数调用它,你会得到相同的结果。State# RealWorld 只有一个,所以你不能用相同的参数调用它,所以这个性质成立。
  2. 用不同的求值顺序计算两个调用,不影响结果。State# RealWorld 只有一个,你不能用不同的顺序计算两个调用,所以这个性质成立。

仔细想想这两件事。

有没有副作用都没关系的 IO

那么,我们就可以说

但是在 IO f :: IO a 的执行过程当中,可能还会有别的变化,对吧?比如我敲了一行字,sxysxy 吃了一口饭之类的。这个怎么办?

我们把它当成 f 的副作用一部分就行了。

说晕了?

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);