以下简单函数迭代地应用给定的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
m (Maybe a)
之类的东西,因为收集所有pre-Nothing值可能会很昂贵,而且无论如何我都不关心它们。
我检查了Hoogle的签名,但没有显示任何内容。 Maybe
位让我觉得monad变换器在这里可能很有用,但我真的没有直觉来提出细节(还)。
这可能是令人尴尬的容易做到这一点,或者令人尴尬地容易理解为什么不能或不应该这样做,但这不是我第一次将自我尴尬作为一种教学策略。< / p>
更新:我当然可以提供一个谓词,而不是使用(a -> Bool) -> (a -> m a) -> a
:类似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
像{{1}}之类的东西会给我们一个新的数据点。
答案 0 :(得分:9)
许多避免显式递归的内容是组成内置的递归组合器,它通常用于非常通用的未提升值。在Functor,Monad或其他提升类型中执行相同操作有时可以使用基本提升操作,如fmap
,<*>
,>>=
等。在某些情况下,已经存在预先提升的版本,如mapM
,zipWithM
等。其他时候,与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
不幸的是,在这一点上我们有点卡住,因为(据我所知),没有monadic展开,解除它就像takeWhile
一样,并非琐碎。另一件可能有意义的事情是使用(MonadMaybe m) => (b -> m (a, b)) -> b -> m [a]
和伴随的MaybeT
变换器这样的签名进行更广泛的展开,但标准库中也不存在这种情况,monad变换器就是一种坑无论如何,绝望。第三种方法可能是找到一些方法将Maybe
和未知monad概括为MonadPlus
或类似的东西。
当然,可能有其他方法有不同的结构,但我怀疑你可能会发现类似的尴尬与任何需要递归进入“monad”的函数,例如,每一步概念上引入另一层必须使用>>=
,join
等消除
总结:首先检查你写的函数最好是在没有显式递归的情况下表达,使用一个不存在的递归组合器(某些unfoldM
)。您可以自己编写组合子(正如人们对takeWhileM
所做的那样),在Hackage中搜索monadic递归组合器,或者只是按原样保留代码。
答案 1 :(得分:3)
我不想要类似
takeWhile
的monadic版本,因为收集所有pre-Nothing值可能会很昂贵,而且无论如何我都不关心它们。
Monadic-lists takeWhile
不会收集所有pre-Nothing值,除非您明确要这样做。这是"List" package中的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
,this answer用于您链接的问题。
至于您希望实施的功能:
{{1}}