使用"任一"中断递归



在我的项目中,我最终在几个地方使用了以下unfold函数。

unfold :: (a -> Either b a) -> a -> b
unfold f x = either id (unfold f) $ f x

这感觉像是一种非常通用的递归模式(只需对类型为a的值应用一个函数,直到得到Right b(,但我在Hoogle中找不到这样的函数。

它真的没有在任何地方定义吗?

此函数在作为Control的额外包中实现。莫纳德。附加回路

loop :: (a -> Either a b) -> a -> b

循环操作,谓词返回Left作为下一个循环的种子,或返回Right中止循环。

loop (x -> if x < 10 then Left $ x * 2 else Right $ show x) 1 == "16"

额外的包还提供了一个单元版本loopM

作为未来的注意事项,除了按名称搜索Hoogle函数外,您还可以简单地插入类型签名,Hoogle将查找与该签名匹配的函数。我通过在Hoogle中输入(a -> Either a b) -> a -> b找到了上述函数。

如果您想要从extra版本中翻转类型参数的版本,那么monad循环包中也有iterateM_

iterateM_ :: Monad m => (a -> m a) -> a -> m b

这并不能一步解决问题,但它更准确地捕捉到了递归的基本模式。

请注意,里面没有任何特定于Either的内容,这确实让它更难找到。我通过呼啦声找到了它的名字。我不知道它是否存在,但我知道如果这个名字的东西确实存在,它会解决你的问题。我从CCD_ 10中的CCD_。它重复地将一个函数应用于上一步的输出,并使用初始种子值,就像您想要的那样。但是您希望能够在任何时候短路,Either的monad实例就是这样做的,所以像iterateM这样的名称更有意义。除了你不关心所有的中间结果,所以iterateM_这个名字是有意义的。

(此外,事实证明,所有产生中间结果的东西都需要一个流媒体系统来完成。回想起来,这是有道理的,但这也解释了为什么hoogle完全改变了我在结尾添加"_"时显示结果的包。(

现在,就这实际上不是你想要的类型而言:是的,当它变得更通用时,有点丢失了。但这可以用一种有趣的方式来恢复。回顾iterateM_的类型,注意返回值是m b,并且b以前在该类型中没有提到。这是一个微妙但重要的信息。由于函数不可能只组成一个它一无所知的多态类型的值,这意味着它永远不会产生这样的值。让我们在类型中内联Either,看看会发生什么:iterateM_ :: (a -> Either r a) -> a -> Either r b。如何安全地将Either r b转换为r?好吧,我们知道我们可以为b选择任何东西,因为它不可能存在。幸运的是,有一些工具可以处理这个问题。

base包含一个模块Data.Void,它在这里提供帮助。它的类型Void没有构造函数。你可以把它看作是一个类型层面的能指,某种事情不会发生。由于这是不可能发生的,因此有一个适配器可以根据需要在值级别上将事情组合在一起:absurd :: Void -> a。这个名字来源于逻辑,基于这样一种想法,即一旦不可能的事情发生,一旦你陷入愚蠢的境地,还不如允许其他事情发生。在操作级别,您可以在Haskell中拥有Void类型的值,因为undefined :: Void将键入check。但无论如何,absurd都是完全安全的——它迫使人们对其论点进行评估。由于任何时候实际计算它时,参数都必须是某种类型的底值,因此这仍然是类型安全的。

那么,这如何与iterateM_相适应呢?好吧,事情已经解决了,所以有一个Either r b值,我们知道它必须是Left构造函数,但使用fromLeft感觉很脏。但有趣的是,虽然r受到上下文的约束,但b仍然是完全多态的。我们可以选择我们想要的任何类型,因为我们知道它永远不会发生。所以选择Void,给出either id absurd :: Either r Void -> r

unfold :: (a -> Either b a) -> a -> b
unfold f x = either id absurd $ iterateM_ f x

也许这甚至不是对你原来的改进。也许是这样——递归是在组合子中捕获的,而不是显式的。值得一提的是,该组合子捕获了比Either特定的递归模式更通用的递归模式。但这需要引入额外的转换步骤来重新调整类型,而这一步骤最终涉及到一个以前不必要的新想法。从好的方面来说,它很好地说明了如何使用类型系统来清晰地传达信息。

最新更新