Следующая простая функция применяет данную монадическую функцию итеративно, пока она не ударит ничто, и в этот момент она возвращает последнее значение, отличное от Ничего. Он делает то, что мне нужно, и я понимаю, как это работает.Избегание явной рекурсии в Haskell
lastJustM :: (Monad m) => (a -> m (Maybe a)) -> a -> m a
lastJustM g x = g x >>= maybe (return x) (lastJustM g)
В рамках моего самообразования в Haskell Я пытаюсь избежать явной рекурсии (или, по крайней мере, понять, как это сделать) всякий раз, когда я могу. Похоже, в этом случае должно быть простое неявное рекурсивное решение, но мне сложно понять это.
Я не хочу, чтобы что-то вроде a monadic version из takeWhile
, так как это может быть дорого собрать все значения до-Ничего, и я все равно их не волнует.
Я проверил Hoogle на подпись, и ничего не появляется. Сбой m (Maybe a)
заставляет меня думать, что здесь может быть полезен трансформатор монады, но у меня нет действительно интуиций, которые мне нужны, чтобы придумать детали (пока).
Возможно, это или смущающе легко сделать это, или смущающе легко понять, почему это невозможно или не должно быть сделано, но это было бы не в первый раз, когда я использовал самосознание как педагогическую стратегию.
Update: Я мог бы, конечно, обеспечить предикат вместо использования 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
даст нам новую точку данных.
'lastJustM = FIX (.. LiftM2 ап ((>> =)) (флип (возможно, вернется))..)'. (ОК, я обманул с помощью 'pointfree'.) – kennytm
@KennyTM: Спасибо! Я даже не думал попробовать «бесполезно», потому что я понятия не имел, что он сможет справиться с такими вещами. Теперь мне просто нужно выяснить, как это работает. –
Существует алгоритм для сокращения * ничего * до беспутной формы с учетом комбинации комбинаторов; это то, что использует 'pointfree'. Конечно, результат может быть или не быть полезным :) –