繁体   English   中英

避免在Haskell中显式递归

[英]Avoiding explicit recursion in Haskell

下面的简单函数迭代地应用给定的monadic函数,直到它命中Nothing,此时它返回最后一个非Nothing值。 它做我需要的,我理解它是如何工作的。

lastJustM :: (Monad m) => (a -> m (Maybe a)) -> a -> m a
lastJustM g x = g x >>= maybe (return x) (lastJustM g)

作为我在Haskell的自我教育的一部分,我试图尽可能避免明确的递归(或至少理解如何)。 在这种情况下,似乎应该有一个简单的非显式递归解决方案,但我无法搞清楚。

我不想要类似于takeWhile 版本takeWhile ,因为收集所有pre-Nothing值可能会很昂贵,而且无论如何我都不关心它们。

我检查了Hoogle的签名,没有任何显示。 m (Maybe a)位让我觉得monad变换器在这里可能很有用,但我真的没有直觉来提出细节(还)。

这可能要么是令人尴尬地容易做到这一点,要么令人尴尬地容易理解为什么不能或不应该这样做,但这不是我第一次将自我尴尬作为一种教学策略。

更新:我当然可以提供谓词而不是使用Maybe :类似(a -> Bool) -> (a -> ma) -> a (返回谓词为true的最后一个值)也可以正常工作。 我感兴趣的是使用标准组合器编写没有显式递归的任一版本的方法。


背景:这是一个简化的上下文工作示例:假设我们对单位广场中的随机游走感兴趣,但我们只关心退出点。 我们有以下步骤功能:

randomStep :: (Floating a, Ord a, Random a) =>
              a -> (a, a) -> State StdGen (Maybe (a, a))
randomStep s (x, y) = do
  (a, gen') <- randomR (0, 2 * pi) <$> get
  put gen'
  let (x', y') = (x + s * cos a, y + s * sin a)
  if x' < 0 || x' > 1 || y' < 0 || y' > 1
    then return Nothing
    else return $ Just (x', y')

evalState (lastJustM (randomStep 0.01) (0.5, 0.5)) <$> newStdGen会给我们一个新的数据点。

避免显式递归的很多内容是组成内置的递归组合器,它通常用于非常通用的未提升值。 在Functor,Monad或其他提升类型中执行相同操作有时可以使用基本提升操作,如fmap<*>>>=等。 在某些情况下,已经存在预先提升的版本,如mapMzipWithM等。 其他时候,与takeWhile ,提升并takeWhile ,也没有提供内置版本。

你的功能确实具有应该成为标准组合器的升级版本的特性。 首先,让我们去掉monad来重建你隐含提升的函数:

lastJust :: (a -> Maybe a) -> a -> a

这里的“最后”这个词给了我们一个提示; 非显式递归通常使用临时列表作为控制结构。 所以你想要的是last应用到迭代函数生成的列表,直到得到Nothing 通过类型的略微概括,我们找到了生成器:

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

所以我们有这样的事情:

dup x = (x, x)
lastJust f x = last $ unfoldr (fmap dup . f) x

不幸的是,在这一点上,我们有点卡住了,因为(据我所知)没有takeWhile展开并解除它,就像takeWhile一样,并非琐碎。 另一件可能有意义的事情是更一般的展开,签名如(MonadMaybe m) => (b -> m (a, b)) -> b -> m [a]和伴随的MaybeT变换器,但这不是也不存在于标准库中,monad变换器无论如何都是绝望之坑。 第三种方法可能是找到一些方法将Maybe和未知monad概括为MonadPlus或类似的东西。

当然,可能有其他方法具有不同的结构,但我怀疑你可能会发现任何需要递归进入“monad”的函数的类似尴尬,例如,每一步在概念上引入了另一层必须是用>>=join等消除

总结:首先检查你写的函数最好是在没有显式递归的情况下表达,使用一个不存在的递归组合器(某些展开的unfoldM )。 您可以自己编写组合子(就像人们使用takeWhileM ),在Hackage上搜索monadic递归组合器,或者只是保留代码。

我不想要类似于takeWhile版本的takeWhile ,因为收集所有pre-Nothing值可能会很昂贵,而且无论如何我都不关心它们。

takeWhile -lists takeWhile不会收集所有pre-Nothing值,除非您明确要这样做。 这将是来自“List”包takeWhile ,用于回答您链接的问题。

至于你想要实现的功能:

{-# LANGUAGE ScopedTypeVariables #-}

import Control.Monad.ListT (ListT) -- from "List" package on hackage
import Data.List.Class (takeWhile, iterateM, lastL)
import Prelude hiding (takeWhile)

thingM :: forall a m. Monad m => (a -> Bool) -> (a -> m a) -> m a -> m a
thingM pred stepM startM =
    lastL $ takeWhile pred list
    where
        list :: ListT m a
        list = iterateM stepM startM

暂无
暂无

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

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