Haskell中do记法不一致问题:为何部分函数合法部分不合法?
这问题问到点子上了!核心在于Haskell的do表达式是依赖具体Monad实例的——不同Monad下,do的行为、类型要求完全不一样。咱们逐个拆解开看:
函数1:为什么它合法?
先看代码:
-- function 1 myfunc :: String myfunc = do x <- (return True) show x
首先,它的类型签名:: String直接把整个do块的上下文锁定在了List Monad里(因为String本质就是[Char],而列表[]是Monad的实例)。
这里的return True可不是IO Monad里的那个return!Haskell的return是多态的,属于Monad类型类——当前上下文是List Monad,所以return会把值包装成单元素列表,也就是return True等价于[True]。
接下来x <- [True],在List Monad里,<-就是遍历列表的元素(这里只有一个元素True),把x绑定成True。最后一行show x生成字符串"True",正好是String类型,完美匹配签名要求。
简单说:函数1全程在List Monad里玩,每一步都符合List Monad的规则,所以合法。
函数2:为什么不合法?
代码如下:
-- function 2 myfunc :: String myfunc = do x <- getLine show x
这里有两个致命的类型冲突:
- Monad上下文矛盾:
getLine是IO Monad的操作(类型IO String),但你的类型签名要求整个do块是String(List Monad)——IO和List是完全不同的Monad,根本没法混用。 - IO Monad规则违反:就算你删掉类型签名,
do块因为用了getLine会进入IO Monad,但IO Monad要求do块的最后一行必须是IO a类型,而你直接写了show x(纯值String),没有把它包装进IO上下文。
编译器报错的本质就是:它既无法把IO类型转换成List类型,也无法把纯String当成IO类型来处理,所以直接报错。
函数3:为什么它合法?
代码:
-- function 3 myfunc = do x <- getLine return (show x)
这个函数全程在IO Monad里:
getLine是IO String,所以do块的上下文被锁定为IO Monad。x <- getLine把x绑定成IO操作返回的String。- 最后一行
return (show x)把show x生成的纯String包装成IO String(IO Monad的return就是把纯值放进IO上下文)。
整个流程完全符合IO Monad的规则,编译器能自动推断出它的类型是IO String,自然合法。
核心总结
函数1和函数3的本质区别是:它们分别用了不同的Monad——函数1用List Monad,函数3用IO Monad,各自遵循对应Monad的规则;而函数2既混用了不同Monad,又违反了IO Monad的要求,所以不合法。
内容的提问来源于stack exchange,提问作者Gertjan Brouwer

