以下是相互递归函数对的两个示例。第一个示例终止并生成预期结果。第二个例子类似,只是它使用了Maybe monad。 fun1'在被叫时不会终止。
fun1 = 1 + fun2
fun2 = let x = fun1
in 2
-- terminates. result is 3.
fun1' = do a <- Just 1
b <- fun2'
return $ a + b
fun2' = do x <- fun1'
return 2
-- does not terminate.
这是另外两个例子。再次,第一个示例以预期结果终止,第二个示例(使用Maybe monad)不会终止。
fun1'' = fun2'' : [1]
fun2'' = (head . tail $ fun1'') + 1
-- terminates. result is [2,1]
fun1''' = do a <- Just [1]
b <- fun2'''
return $ b : a
fun2''' = do x <- fun1'''
return $ (head . tail $ x) + 1
-- does not terminate.
我相信我的情况在语义上类似于我真实代码中的最后一个例子。我有什么选择让它终止?我会被迫放弃Maybe monad吗?
更新 这是我最终使用的解决方案;
fun1'''' = do a <- Just [1]
b <- fun2''''
return $ b : a
fun2'''' = do return $ (head . tail . fromJust $ fun1'''') + 1
-- terminates :)
关键区别在于fun2''''
不再使用绑定运算符在fun1''''
上运行。相反,它明确使用fromJust(并假设fun1''''
不是Nothing
)。
在我的真实代码中fun2
实际上调用了许多其他函数。这些其他函数与fun2
不是相互递归的,并且可能会返回Nothing结果。幸运的是,我仍然可以在do notation中隐式使用bind运算符来访问其他所需的值。
答案 0 :(得分:4)
fun1'/fun2'
没有终止的原因是Maybe
monad的绑定操作(>>=
)需要检查第一个参数是Nothing
还是Just (...)
{1}}。因此,当您在x <- fun1'
中fun2'
时,即使您未使用x
,仍需要检查fun1'
是Nothing
还是Just (...)
(你不关心(...),它会绑定到你不使用的x
)。但要检查一下,您需要知道fun2'
是Just
还是Nothing
,因为您在fun1'
(b <- fun2'
中绑定了b的结果}) - &gt;无限递归。
在第一种情况下不会发生同样的情况,因为在fun2
中,您不使用x
,因此永远不需要评估fun1
!
答案 1 :(得分:2)
这里的问题是你的终止函数fun1
并不是相互排斥的,即使它似乎是。
事实上,您的fun2'
函数实际上只是引用了值2
。例如:
λ> let x = undefined in 2
2
undefined
部分根本没有得到评估。因此,x = fun1
函数根本不会对您的fun2
进行评估,因此fun2
评估为2
后,它会成功终止。
而在fun2'
示例中,它不会降低到任何值。所以,它不会终止。事实上,如果您实际上将fun2'
函数转换为let表达式,就像您的fun2
示例一样,它将终止:
fun2' = let x = fun1'
in (Just 2)
fun2''
是一个相互递归的函数,它引用值2
:
λ> tail fun1''
[1]
λ> head $ tail fun1''
1
λ> (head $ tail fun1'') + 1
2
因此fun2''
实际上是指值2
。
答案 2 :(得分:1)
我认为你别无选择,只能放弃也许monad。
请记住,monads只表示可以链接在一起的计算序列。 Maybe
monad表示可能失败的计算,当序列中的某个计算失败时,我们最终得到Nothing
。虽然看起来x
的值不是必需的(因此我们期望程序因懒惰的估值而停止),但它是一个序列,因此必须进行评估,从而产生无限递归
而是试试这个:
fun1' = do a <- Just 1
b <- fun2'
return $ a + b
fun2' = do Nothing
fun1'
return 2
main = print $ fun1'
这将停止,因为Nothing
将绕过所有其他计算。
答案 3 :(得分:1)
如果您需要将Maybe
monad与相互递归组合在一起,那么您可能需要RecursiveDo
notation,它允许mdo
块内的单值计算值以递归方式相互引用(对于支持MonadFix
类的monad)。以下内容汇编并给出了您可能期望的结果Just ([2,1],2)
。
{-# LANGUAGE RecursiveDo #-}
maybeFuns = mdo
fun1''' <- do a <- Just [1]
return $ fun2''' : a
fun2''' <- return $ (head . tail $ fun1''') + 1
return (fun1''', fun2''')
您需要在一个mdo
块中定义它,而不是单独的函数。如果您不关心如果某个部分评估为maybeFuns =
时出错,则可以 将Just (fun1''', fun2''') =
替换为Nothing
。