Многое из того, что позволяет избежать явной рекурсии, - это составление встроенных рекурсивных комбинаторов, которые обычно работают с очень общими не поднятыми значениями. Выполнение того же действия в Functor, Monad или другом поднятом типе иногда работает с использованием базовых операций поднятия, таких как fmap
, <*>
, >>=
и т. В некоторых случаях уже имеется предварительно поднятая версия, например, mapM
, zipWithM
и т. Д. В других случаях, как и в случае takeWhile
, подъем не является тривиальным, и встроенная версия не предоставляется.
Ваша функция действительно имеет характеристики чего-то, что должно быть поднятой версией стандартных комбинаторов. Итак, во-первых, давайте разберем монаду, чтобы восстановить функцию, которую вы неявно поднимаете:
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
, не тривиально. Другая вещь, которая может иметь смысл, - это более общее развертывание с сигнатурой типа (MonadMaybe m) => (b -> m (a, b)) -> b -> m [a]
и сопровождающим преобразователем MaybeT
, но этого также нет в стандартных библиотеках, и преобразователи монад в любом случае являются своего рода Ямой Отчаяния. Третий подход может заключаться в том, чтобы найти способ обобщить и Maybe
, и неизвестную монаду как MonadPlus
или что-то подобное.
Конечно, могут быть и другие подходы с другой структурой, но я подозреваю, что вы, вероятно, найдете похожую неловкость с любой функцией, которая должна быть рекурсивной, входящей «в» монаду, например, каждый шаг концептуально вводит другой уровень которые должны быть устранены с помощью >>=
, join
и т. д.
В итоге: при первой проверке ваша функция в том виде, в котором она написана, лучше всего выражается без явной рекурсии, используя рекурсивный комбинатор (некоторый вариант unfoldM
), который не существует. Вы можете написать комбинатор самостоятельно (как это сделали люди с takeWhileM
), покопаться в Hackage для монадических рекурсивных комбинаторов, или просто оставить свой код как есть.