繁体   English   中英

使用Maybe monad终止相互递归的函数

[英]Termination of mutually recursive functions using the Maybe monad

以下是相互递归函数对的两个示例。 第一个示例终止并生成预期结果。 第二个例子类似,只是它使用了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''''不再使用bind运算符对fun1''''进行操作。 相反,它明确使用fromJust(并假设fun1''''不是Nothing )。

在我的真实代码中, fun2实际上调用了许多其他函数。 这些其他函数与fun2不是相互递归的,并且可能会返回Nothing结果。 幸运的是,我仍然可以在do表示中隐式使用bind运算符来访问其他所需的值。

fun1'/fun2'没有终止的原因是Maybe monad的绑定操作( >>= )需要检查第一个参数是否为Nothing或者是否为Just (...) 所以当你在fun2'x <- fun1'时,即使你不使用x ,你还需要检查fun1'Nothing还是Just (...) (你不关心(...) ),它将绑定到你不使用的x )。 但要检查一下,你需要知道fun2'Just还是Nothing ,因为你在fun1'b <- fun2' ) - >无限递归中将b绑定到它的结果。

在第一种情况下不会发生同样的情况,因为在fun2 ,你不使用x ,所以fun1永远不需要被评估!

这里的问题是你的终止函数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

我想你别无选择,只能放弃Maybe 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将绕过所有其他计算。

如果你需要将Maybe monad与相互递归结合起来,那么也许你想要RecursiveDo表示法 ,它允许mdo块中的monadically计算值以递归方式相互引用(对于支持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 = by Just (fun1''', fun2''') =如果你不关心如果某个部分评估为Nothing出错。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM