Monad Transformers理解及do记法示例代码相关疑问求助
解答
1. 类型变量s和a的含义
你首先混淆的点是类型变量的命名,我们逐个拆解:
- 基础类型
ProtectedData a的两个字段分别是固定String类型的密码,和类型为a的受保护值,密码本身没有做类型参数化,永远是字符串。 - 转换器栈类型
Protected s a = MaybeT (Reader (ProtectedData s)) a里,第一个类型参数s是环境中ProtectedData存储的值的类型,对应上面ProtectedData的参数a;第二个类型参数a是整个Protected计算执行完成后返回的结果类型。 access函数的签名access :: String -> Protected a a只是把类型变量名复用了,等价于access :: String -> Protected s s,意思就是这个操作返回的值的类型,和环境里存的受保护值类型完全一致——因为这个函数的作用就是把存的值取出来,所以两个参数刚好相同。
2. run函数的逻辑
你的理解完全正确,这个函数就是逐层拆解monad转换器栈:
- 先调用
runMaybeT脱掉最外层的MaybeT包装,得到下层的Reader (ProtectedData s) (Maybe a)类型值 - 再调用
runReader把Reader层脱掉,传入你准备好的ProtectedData实例作为环境,最后得到Maybe a类型的结果,密码正确就是Just 结果,错误就是Nothing。
3. access函数相关疑问
函数作用
你第二个猜测是对的:
- 它不是用来注入密码和数据的,注入是你调用
run函数传入ProtectedData实例的时候完成的 - 它的作用就是接收你输入的密码,尝试读取环境里的受保护数据,密码错误时整个
Protected计算直接返回Nothing,也就是失败。
为什么要写lift ask
monad转换器栈有一个核心规则:do块内的所有操作,都必须属于当前栈顶的monad类型。
你当前用的Protected栈结构是:MaybeT在最顶层,下层是Reader (ProtectedData s)。
ask是下层Readermonad原生的操作,类型是Reader (ProtectedData s) (ProtectedData s),不属于顶层的MaybeT,直接写pd <- ask会类型不匹配报错。lift的作用就是把下层monad的操作抬升到上层monad的上下文中,lift ask之后的类型就变成了MaybeT (Reader (ProtectedData s)) (ProtectedData s),刚好和当前Protected的类型匹配,这样就可以用<-拿到内部的ProtectedData实例pd。
后续的逻辑也很顺:
- 用传入的密码和拿到的
pd调用accessData,得到Maybe a类型的结果v return v把v包装到Readermonad中,得到Reader (ProtectedData s) (Maybe a)- 最后用
MaybeT包装一层,就得到符合签名的Protected s a类型返回值。
实际运行示例
-- 定义存了Int值的受保护数据,密码为123456 mySecret :: ProtectedData Int mySecret = ProtectedData "123456" 996 -- 密码正确的情况 testRight :: Maybe Int testRight = run mySecret (access "123456") -- 执行结果为 Just 996 -- 密码错误的情况 testWrong :: Maybe Int testWrong = run mySecret (access "wrongpass") -- 执行结果为 Nothing
内容的提问来源于stack exchange,提问作者Superschnitzel
相关产品推荐
相关产品推荐

