下面这个简单的函数会反复应用一个给定的单子函数,直到遇到Nothing为止,此时它会返回最后一个非Nothing值。这正是我所需要的,并且我知道它是如何工作的。
lastJustM :: (Monad m) => (a -> m (Maybe a)) -> a -> m a
lastJustM g x = g x >>= maybe (return x) (lastJustM g)
作为学习Haskell的一部分,我试图在适当时候避免使用显式递归(或者至少理解如何避免)。在这种情况下,似乎应该有一个简单的非显式递归解决方案,但我很难想明白。我不想要像takeWhile的monadic版本那样的东西,因为收集所有pre-Nothing值可能是昂贵的,而且我也不关心它们。
我检查了Hoogle的签名,没有显示出任何东西。`m(Maybe a)`的部分让我觉得单子变换器在这里可能有用,但我还没有需要提供细节的直觉(尚未)。
这可能很容易做到,或者很容易看出为什么不能或不应该这样做,但这不会是我作为教育策略使用自我尴尬的第一次。
更新:当然,我可以提供谓词,而不是使用Maybe:类似于`(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
这样的代码将会给我们提供一个新的数据点。
pointfree
,因为我不知道它能处理这种情况。现在我只需要弄清楚它是如何工作的。 - Travis Brownpointfree
所使用的。当然,结果可能有用也可能没用 :) - Antal Spector-Zabuskypointfree
的正确术语是“无点风格”。而且,liftM2
在((->) r)
单子中工作吗?这总是提高代码的清晰度... - C. A. McCannfix
的真正问题在于它除了“如果我搞砸了就崩溃程序”之外没有任何语义价值。而map
和fold
等函数的价值在于,只要你不对无限列表进行左折叠,它们完美地定义了它们执行的操作,并且不会让你走上错误的道路。 - C. A. McCann