使用let与do处理monadic值的差异及行为原因解析
Let vs Do Syntax with Monadic Values (IO Random Numbers)
这是个很典型的Haskell monad使用误区,咱们一步步拆解为什么会出现这样的差异:
先分析dupRand的行为
dupRand = let i = randomRIO (1, 10) in sequence [i, i]
这里的i绑定的不是随机数的结果,而是randomRIO (1,10)这个IO动作本身。sequence [i,i]的作用是依次执行列表里的每个IO动作,然后把结果收集成列表。所以这里相当于连续执行了两次randomRIO (1,10)——每次执行都会消耗随机数生成器的内部状态,自然会得到两个不同的随机数。
你可以把它等价写成更直白的绑定形式:
dupRand = randomRIO (1,10) >>= \x -> randomRIO (1,10) >>= \y -> pure [x,y]
这样就能明显看到,我们调用了两次随机数生成器,结果肯定是不一样的。
再看dupRand2的行为
dupRand2 = do i <- randomRIO (1, 10); pure [i, i]
这里的i <- randomRIO...是do语法的核心绑定逻辑:它会执行这个IO动作,把动作产生的实际结果(也就是那个具体的随机数)绑定给变量i。之后的pure [i,i]只是把这个已经拿到的随机数重复放进列表里,完全没有再次执行IO动作。
等价的显式绑定写法是:
dupRand2 = randomRIO (1,10) >>= \i -> pure [i,i]
这里只执行了一次随机数生成动作,结果被复用了两次,所以列表里的两个数必然是相同的。
核心区别总结
- 在
let绑定中,你绑定的是monadic动作本身,每次使用这个绑定变量,都会重新执行一次该动作(对于IO这种带有状态/副作用的monad,每次执行的结果很可能不同)。 - 在do语法的
<-绑定中,你绑定的是monadic动作执行后的结果,后续使用这个变量都是复用已经计算好的结果,不会重新触发动作执行。
内容的提问来源于stack exchange,提问作者zli
相关产品推荐
相关产品推荐

