Control.Monad.Fix
| Авторские права | (c) Andy Gill 2001 (c) Oregon Graduate Institute of Science and Technology 2002 |
|---|---|
| Лицензия | в стиле BSD (см. файл libraries/base/LICENSE) |
| Сопровождающий | libraries@haskell.org |
| Стабильность | стабильная |
| Переносимость | переносимая |
| Безопасный Haskell | Safe |
| Язык | Haskell2010 |
Описание
Монадические неподвижные точки, используемые для устранения синтаксического сахара {-# LANGUAGE RecursiveDo #-}.
Рассмотрим обобщённую версию так называемой задачи repmin (заменить на минимум): накапливать элементы контейнера в Monoid и изменять каждый элемент, используя итоговый аккумулятор.
repmin :: (Functor t, Foldable t, Monoid b) => (a -> b) -> (a -> b -> c) -> t a -> t c repmin f g as = fmap (`g` foldMap f as) as
Наивная реализация, приведённая выше, выполняет два обхода. Можно ли сделать лучше и достичь цели за один проход? На первый взгляд это невозможно, поскольку нам нужно было бы знать будущее, но на помощь приходит ленивое вычисление:
import Data.Traversable (mapAccumR) repmin :: (Traversable t, Monoid b) => (a -> b) -> (a -> b -> c) -> t a -> t c repmin f g as = let (b, cs) = mapAccumR (\acc a -> (f a <> acc, g a b)) mempty as in cs
Как проверить, что repmin действительно выполняет только один обход? Запустим её на бесконечном входе:
>>> import Data.Monoid (All(..))
>>> take 3 $ repmin All (const id) ([True, True, False] ++ undefined)
[All {getAll = False},All {getAll = False},All {getAll = False}]
Пока всё хорошо, но можно ли обобщить g, чтобы возвращать монадическое значение a -> b -> m c? Следующий вариант не работает: он сообщает, что b не находится в области видимости:
import Data.Traversable (mapAccumM) repminM :: (Traversable t, Monoid b, Monad m) => (a -> b) -> (a -> b -> m c) -> t a -> m (t c) repminM f g as = do (b, cs) <- mapAccumM (\acc a -> (f a <> acc,) <$> g a b) mempty as pure cs
Чтобы разгадать эту загадку, перепишем repmin через fix:
repmin :: (Traversable t, Monoid b) => (a -> b) -> (a -> b -> c) -> t a -> t c repmin f g as = snd $ fix $ \(b, cs) -> mapAccumR (\acc a -> (f a <> acc, g a b)) mempty as
Теперь можно заменить fix на mfix и получить решение:
repminM :: (Traversable t, Monoid b, MonadFix m) => (a -> b) -> (a -> b -> m c) -> t a -> m (t c) repminM f g as = fmap snd $ mfix $ \(~(b, cs)) -> mapAccumM (\acc a -> (f a <> acc,) <$> g a b) mempty as
Например:
>>> import Data.Monoid (Sum(..)) >>> repminM Sum (\a b -> print a >> pure (a + getSum b)) [3, 5, 2] 3 5 2 [13,15,12]
Удивительно, но GHC способен выполнить это преобразование автоматически, если включён {-# LANGUAGE RecursiveDo #-}. В частности, работает следующая реализация repminM (обратите внимание: используется mdo вместо do):
{-# LANGUAGE RecursiveDo #-}
repminM
:: (Traversable t, Monoid b, MonadFix m)
=> (a -> b) -> (a -> b -> m c) -> t a -> m (t c)
repminM f g as = mdo
(b, cs) <- mapAccumM (\acc a -> (f a <> acc,) <$> g a b) mempty as
pure cs
Дополнительная литература:
- Руководство пользователя GHC, рекурсивная do-нотация.
- Вики Haskell, MonadFix.
- Levent Erkök, Рекурсия по значениям в монадических вычислениях, Oregon Graduate Institute, 2002.
- Levent Erkök, John Launchbury, Рекурсивный do для Haskell, Haskell '02, 29–37, 2002.
- Richard S. Bird, Использование циклических программ для устранения многократных обходов данных, Acta Informatica 21, 239–250, 1984.
- Jasper Van der Jeugt, Ленивая раскладка, 2023.
class Monad m => MonadFix (m :: Type -> Type) where Источник
Монады с неподвижными точками и семантикой «завязывания узла». Экземпляры MonadFix должны удовлетворять следующим законам:
- Сохранение чистоты
mfix (return . h) = return (fix h)- Левое сокращение (или сужение)
mfix (\x -> a >>= \y -> f x y) = a >>= \y -> mfix (\x -> f x y)- Сдвиг
-
mfix (liftM h . f) = liftM h (mfix (f . h))для строгогоh. - Вложенность
mfix (\x -> mfix (\y -> f x y)) = mfix (\x -> f x x)
Этот класс используется при преобразовании рекурсивной нотации do, поддерживаемой GHC и Hugs.
Экземпляры
Определён в Data.Functor.Product Методыmfix :: (a -> Product f g a) -> Product f g a Исходный код | |
| (MonadFix f, MonadFix g) => MonadFix (f :*: g) Исходный код | Начиная с: base-4.9.0.0 |
Определён в GHC.Internal.Control.Monad.Fix Методыmfix :: (a -> (f :*: g) a) -> (f :*: g) a Исходный код | |
| MonadFix ((->) r) Исходный код | Начиная с: base-2.1 |
Определён в GHC.Internal.Control.Monad.Fix Методыmfix :: (a -> r -> a) -> r -> a Исходный код | |
| MonadFix f => MonadFix (M1 i c f) Исходный код | Начиная с: base-4.9.0.0 |
Определён в GHC.Internal.Control.Monad.Fix Методыmfix :: (a -> M1 i c f a) -> M1 i c f a Исходный код | |
fix :: (a -> a) -> a Исходный код
fix f — это наименьшая неподвижная точка функции f, то есть наименьшее определённое x, такое что f x = x.
Если f является строгой, это означает, что поскольку по определению строгости f ⊥ = ⊥, наименьшая определённая неподвижная точка любой строгой функции — ⊥.
Примеры
Функцию факториала можно записать с помощью прямой рекурсии так:
>>> let fac n = if n <= 1 then 1 else n * fac (n-1) in fac 5 120
Здесь используется тот факт, что let в Haskell вводит рекурсивные связывания. Это определение можно переписать с использованием fix:
Вместо рекурсивного вызова мы вводим фиктивный параметр rec; при использовании внутри fix этот параметр ссылается на аргумент fix, тем самым вновь вводя рекурсию.
>>> fix (\rec n -> if n <= 1 then 1 else n * rec (n-1)) 5 120
Используя fix, можно реализовать версии repeat как fix . (:) и cycle как fix . (++).
>>> take 10 $ fix (0:) [0,0,0,0,0,0,0,0,0,0]
>>> map (fix (\rec n -> if n < 2 then n else rec (n - 1) + rec (n - 2))) [1..10] [1,1,2,3,5,8,13,21,34,55]
Подробности реализации
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/9.14.1/docs/libraries/base-4.22.0.0-66f8/Control-Monad-Fix.html