Мы можем использовать функцию связывания здесь:
import Data.Bool(bool)
import Control.Monad(liftM2)
fixedM :: (Eq a, Monad m) => (a -> m a) -> a -> m a
fixedM f = go
where go x = f x >>= (liftM2 bool go pure <*> (x ==))
Более подробная реализация:
fixedM :: (Eq a, Monad m) => (a -> m a) -> a -> m a
fixedM f x = do
x' <- f x
if x == x'
then pure x'
else fixedM f x'
Таким образом, мы сначала вычисляем x'
с помощью f x
.Если f x
вернуть Just x'
, то мы продолжаем.В случае, если f x
вернет Nothing
, fixedM
также вернет Nothing
.Затем мы сравниваем x
с x'
.Если они равны, мы возвращаем pure x'
, в противном случае мы возвращаемся к fixedM f x'
.
В качестве альтернативы, мы можем использовать сопоставление с шаблоном , хотя это в основном делает привязкуявный оператор (и работает только для Maybe
):
import Control.Monad(ap)
fixedM :: Eq a => (a -> Maybe a) -> a -> Maybe a
fixedM f = ap go f
where go x (Just x') | x == x' = go x' (f x')
| otherwise = Just x'
go _ _ = Nothing
мы можем сделать его более компактным, используя шаблонные ограждения :
fixedM :: Eq a => (a -> Maybe a) -> a -> Maybe a
fixedM f = go
where go x | Just x' <- f x = bool (go x) (Just x) (x == x')
| otherwise = Nothing