为什么回调堆栈大小超过我使用的绑定语法?

Why does the callback stack size exceed depending on the bind syntax I use?

我有两个代码变体,应该只是语法不同,但也许我错了。第一个导致“回调堆栈大小超过”/“太多递归”。

main :: Effect Unit
main = do
  w <- window
  loop w

loop :: Window -> Effect Unit
loop w = redraw <* requestAnimationFrame (loop w) w

redraw :: Effect Unit
redraw = log "Redrawing endlessly!"

通过像这样改变loop函数,我可以避免这个问题:

loop :: Window -> Effect Unit
loop = do
  redraw
  requestAnimationFrame (loop w) w $> unit

为什么?

loop 函数的第一个版本总是立即无条件地调用自身:

loop w = redraw <* requestAnimationFrame (loop w) w
                                         ^^^^^^^^
                                          right here

每次有人调用 loop 时,它会立即调用 loop w。它必须立即调用 loop w,因为它需要将其 return 值作为第一个参数传递给 requestAnimationFrame,它需要将第二个参数传递给运算符 <*,这它需要调用以提供自己的 return 值。无限循环,就在那里。

然而,do 语法被脱糖为 lambda-expression 和对运算符 >>= 的调用,如下所示:

loop w = 
    redraw >>= (\x -> requestAnimationFrame (loop w) w $> unit)

这里,递归调用 loop w 不会立即发生,而是被包装在 lambda-expression 中,然后作为第二个参数传递给运算符 >>=。只有在计算 lambda 表达式的主体时才会发生递归调用。