Следующая простая функция применяет данную монадическую функцию итеративно до тех пор, пока она не ударит ничто, и в этот момент она возвращает последнее значение, отличное от Ничего. Он делает то, что мне нужно, и я понимаю, как это работает.
lastJustM :: (Monad m) => (a -> m (Maybe a)) -> a -> m a
lastJustM g x = g x >>= maybe (return x) (lastJustM g)
В рамках моего самообразования в Haskell я стараюсь избегать явной рекурсии (или, по крайней мере, понимать, как это сделать), когда я могу. Похоже, в этом случае должно быть простое неявное рекурсивное решение, но мне трудно понять его.
Мне не нужно что-то вроде монадической версии takeWhile
, так как было бы дорого собрать все значения 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
даст нам новую точку данных.