Spec-Zone.ru › Haskell 9

Data.Traversable

Авторские права Conor McBride and Ross Paterson 2005
Лицензия BSD-style (см. файл LICENSE в дистрибутиве)
Сопровождающий libraries@haskell.org
Стабильность стабильная
Переносимость переносимая
Safe Haskell Safe
Язык Haskell2010

Содержание

  • Класс Traversable
  • Вспомогательные функции
  • Общие определения для методов суперкласса
  • Обзор
    • Методы traverse и mapM
      • Их аналоги с Foldable, сохраняющие только эффекты.
      • Кратность результатов
    • Методы sequenceA и sequence
      • Осторожность с реализациями методов по умолчанию
      • Монадическое короткое замыкание
    • Пример экземпляра для бинарного дерева
      • Прямой (pre-order) и обратный (post-order) обход дерева
    • Интуитивное конструирование
  • Продвинутые обходы
    • Приведение типов
    • Identity: функция fmapDefault
    • State: функции mapAccumL, mapAccumR
    • Const: функция foldMapDefault
    • ZipList: транспонирование списков списков
  • Законы
  • См. также

Описание

Класс структур данных, которые можно обойти слева направо, выполняя действие над каждым элементом. Ожидается, что экземпляры удовлетворяют приведенным законам.

Класс Traversable

class (Functor t, Foldable t) => Traversable (t :: Type -> Type) where Исходный код

Функторы, представляющие структуры данных, которые могут быть преобразованы в структуры той же формы путем выполнения действия Applicative (или, следовательно, Monad) над каждым элементом слева направо.

Более подробное описание того, что означает та же форма, различных методов, способов построения обходов и примеров продвинутого использования можно найти в разделе Обзор (Overview) модуля Data.Traversable.

Законы класса см. в разделе Законы (Laws) модуля Data.Traversable.

Минимальное полное определение

traverse | sequenceA

Методы

traverse :: Applicative f => (a -> f b) -> t a -> f (t b) Исходный код

Отображает каждый элемент структуры в действие, вычисляет эти действия слева направо и собирает результаты. Для версии, игнорирующей результаты, см. traverse_.

Примеры
Развернуть

Базовое использование:

В первых двух примерах показано, как каждое вычисленное действие отображается в результирующую структуру.

>>> traverse Just [1,2,3,4]
Just [1,2,3,4]
>>> traverse id [Right 1, Right 2, Right 3, Right 4]
Right [1,2,3,4]

В следующих примерах показано, что значения Nothing и Left приводят к короткому замыканию создаваемой структуры.

>>> traverse (const Nothing) [1,2,3,4]
Nothing
>>> traverse (\x -> if odd x then Just x else Nothing)  [1,2,3,4]
Nothing
>>> traverse id [Right 1, Right 2, Right 3, Right 4, Left 0]
Left 0

sequenceA :: Applicative f => t (f a) -> f (t a) Исходный код

Вычисляет каждое действие в структуре слева направо и собирает результаты. Для версии, игнорирующей результаты, см. sequenceA_.

Примеры
Развернуть

Базовое использование:

В первых двух примерах показано, как sequenceA полностью вычисляет структуру и собирает результаты.

>>> sequenceA [Just 1, Just 2, Just 3]
Just [1,2,3]
>>> sequenceA [Right 1, Right 2, Right 3]
Right [1,2,3]

Следующие два примера показывают, что Nothing и Just вызовут короткое замыкание результирующей структуры, если они присутствуют на входе. Дополнительные сведения см. в экземплярах Traversable для Either и Maybe.

>>> sequenceA [Just 1, Just 2, Just 3, Nothing]
Nothing
>>> sequenceA [Right 1, Right 2, Right 3, Left 4]
Left 4

mapM :: Monad m => (a -> m b) -> t a -> m (t b) Исходный код

Отображает каждый элемент структуры в монадическое действие, вычисляет эти действия слева направо и собирает результаты. Для версии, игнорирующей результаты, см. mapM_.

Примеры
Развернуть

mapM буквально представляет собой traverse с сигнатурой типа, ограниченной Monad. Его реализация может быть более эффективной благодаря дополнительным возможностям Monad.

sequence :: Monad m => t (m a) -> m (t a) Исходный код

Вычисляет каждое монадическое действие в структуре слева направо и собирает результаты. Для версии, игнорирующей результаты, см. sequence_.

Примеры
Развернуть

Базовое использование:

Первые два примера — это случаи, когда входные и выходные данные sequence изоморфны.

>>> sequence $ Right [1,2,3,4]
[Right 1,Right 2,Right 3,Right 4]
>>> sequence $ [Right 1,Right 2,Right 3,Right 4]
Right [1,2,3,4]

Следующие примеры демонстрируют поведение короткого замыкания для sequence.

>>> sequence $ Left [1,2,3,4]
Left [1,2,3,4]
>>> sequence $ [Left 0, Right 1,Right 2,Right 3,Right 4]
Left 0
Экземпляры
Сведения об экземплярах
Traversable Complex Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Complex

Методы

traverse :: Applicative f => (a -> f b) -> Complex a -> f (Complex b) Исходный код

sequenceA :: Applicative f => Complex (f a) -> f (Complex a) Исходный код

mapM :: Monad m => (a -> m b) -> Complex a -> m (Complex b) Исходный код

sequence :: Monad m => Complex (m a) -> m (Complex a) Исходный код

Traversable First Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Semigroup

Методы

traverse :: Applicative f => (a -> f b) -> First a -> f (First b) Исходный код

sequenceA :: Applicative f => First (f a) -> f (First a) Исходный код

mapM :: Monad m => (a -> m b) -> First a -> m (First b) Исходный код

sequence :: Monad m => First (m a) -> m (First a) Исходный код

Traversable Last Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Semigroup

Методы

traverse :: Applicative f => (a -> f b) -> Last a -> f (Last b) Исходный код

sequenceA :: Applicative f => Last (f a) -> f (Last a) Исходный код

mapM :: Monad m => (a -> m b) -> Last a -> m (Last b) Исходный код

sequence :: Monad m => Last (m a) -> m (Last a) Исходный код

Traversable Max Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Semigroup

Методы

traverse :: Applicative f => (a -> f b) -> Max a -> f (Max b) Исходный код

sequenceA :: Applicative f => Max (f a) -> f (Max a) Исходный код

mapM :: Monad m => (a -> m b) -> Max a -> m (Max b) Исходный код

sequence :: Monad m => Max (m a) -> m (Max a) Исходный код

Traversable Min Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Semigroup

Методы

traverse :: Applicative f => (a -> f b) -> Min a -> f (Min b) Исходный код

sequenceA :: Applicative f => Min (f a) -> f (Min a) Исходный код

mapM :: Monad m => (a -> m b) -> Min a -> m (Min b) Исходный код

sequence :: Monad m => Min (m a) -> m (Min a) Исходный код

Traversable NonEmpty Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> NonEmpty a -> f (NonEmpty b) Исходный код

sequenceA :: Applicative f => NonEmpty (f a) -> f (NonEmpty a) Исходный код

mapM :: Monad m => (a -> m b) -> NonEmpty a -> m (NonEmpty b) Исходный код

sequence :: Monad m => NonEmpty (m a) -> m (NonEmpty a) Исходный код

Traversable Identity Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Identity a -> f (Identity b) Исходный код

sequenceA :: Applicative f => Identity (f a) -> f (Identity a) Исходный код

mapM :: Monad m => (a -> m b) -> Identity a -> m (Identity b) Исходный код

sequence :: Monad m => Identity (m a) -> m (Identity a) Исходный код

Traversable First Исходный код

С версии: base-4.8.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> First a -> f (First b) Исходный код

sequenceA :: Applicative f => First (f a) -> f (First a) Исходный код

mapM :: Monad m => (a -> m b) -> First a -> m (First b) Исходный код

sequence :: Monad m => First (m a) -> m (First a) Исходный код

Traversable Last Исходный код

С версии: base-4.8.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Last a -> f (Last b) Исходный код

sequenceA :: Applicative f => Last (f a) -> f (Last a) Исходный код

mapM :: Monad m => (a -> m b) -> Last a -> m (Last b) Исходный код

sequence :: Monad m => Last (m a) -> m (Last a) Исходный код

Traversable Down Исходный код

С версии: base-4.12.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Down a -> f (Down b) Исходный код

sequenceA :: Applicative f => Down (f a) -> f (Down a) Исходный код

mapM :: Monad m => (a -> m b) -> Down a -> m (Down b) Исходный код

sequence :: Monad m => Down (m a) -> m (Down a) Исходный код

Traversable Dual Исходный код

С версии: base-4.8.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Dual a -> f (Dual b) Исходный код

sequenceA :: Applicative f => Dual (f a) -> f (Dual a) Исходный код

mapM :: Monad m => (a -> m b) -> Dual a -> m (Dual b) Исходный код

sequence :: Monad m => Dual (m a) -> m (Dual a) Исходный код

Traversable Product Исходный код

С версии: base-4.8.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Product a -> f (Product b) Исходный код

sequenceA :: Applicative f => Product (f a) -> f (Product a) Исходный код

mapM :: Monad m => (a -> m b) -> Product a -> m (Product b) Исходный код

sequence :: Monad m => Product (m a) -> m (Product a) Исходный код

Traversable Sum Исходный код

С версии: base-4.8.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Sum a -> f (Sum b) Исходный код

sequenceA :: Applicative f => Sum (f a) -> f (Sum a) Исходный код

mapM :: Monad m => (a -> m b) -> Sum a -> m (Sum b) Исходный код

sequence :: Monad m => Sum (m a) -> m (Sum a) Исходный код

Traversable ZipList Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Functor.ZipList

Методы

traverse :: Applicative f => (a -> f b) -> ZipList a -> f (ZipList b) Исходный код

sequenceA :: Applicative f => ZipList (f a) -> f (ZipList a) Исходный код

mapM :: Monad m => (a -> m b) -> ZipList a -> m (ZipList b) Исходный код

sequence :: Monad m => ZipList (m a) -> m (ZipList a) Исходный код

Traversable Par1 Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Par1 a -> f (Par1 b) Исходный код

sequenceA :: Applicative f => Par1 (f a) -> f (Par1 a) Исходный код

mapM :: Monad m => (a -> m b) -> Par1 a -> m (Par1 b) Исходный код

sequence :: Monad m => Par1 (m a) -> m (Par1 a) Исходный код

Traversable GenClosure Исходный код
Сведения об экземпляре

Определено в GHC.Internal.Heap.Closures

Методы

traverse :: Applicative f => (a -> f b) -> GenClosure a -> f (GenClosure b) Исходный код

sequenceA :: Applicative f => GenClosure (f a) -> f (GenClosure a) Исходный код

mapM :: Monad m => (a -> m b) -> GenClosure a -> m (GenClosure b) Исходный код

sequence :: Monad m => GenClosure (m a) -> m (GenClosure a) Исходный код

Traversable GenStackField Исходный код
Сведения об экземпляре

Определено в GHC.Internal.Heap.Closures

Методы

traverse :: Applicative f => (a -> f b) -> GenStackField a -> f (GenStackField b) Исходный код

sequenceA :: Applicative f => GenStackField (f a) -> f (GenStackField a) Исходный код

mapM :: Monad m => (a -> m b) -> GenStackField a -> m (GenStackField b) Исходный код

sequence :: Monad m => GenStackField (m a) -> m (GenStackField a) Исходный код

Traversable GenStackFrame Исходный код
Сведения об экземпляре

Определено в GHC.Internal.Heap.Closures

Методы

traverse :: Applicative f => (a -> f b) -> GenStackFrame a -> f (GenStackFrame b) Исходный код

sequenceA :: Applicative f => GenStackFrame (f a) -> f (GenStackFrame a) Исходный код

mapM :: Monad m => (a -> m b) -> GenStackFrame a -> m (GenStackFrame b) Исходный код

sequence :: Monad m => GenStackFrame (m a) -> m (GenStackFrame a) Исходный код

Traversable GenStgStackClosure Исходный код
Сведения об экземпляре

Определено в GHC.Internal.Heap.Closures

Методы

traverse :: Applicative f => (a -> f b) -> GenStgStackClosure a -> f (GenStgStackClosure b) Исходный код

sequenceA :: Applicative f => GenStgStackClosure (f a) -> f (GenStgStackClosure a) Исходный код

mapM :: Monad m => (a -> m b) -> GenStgStackClosure a -> m (GenStgStackClosure b) Исходный код

sequence :: Monad m => GenStgStackClosure (m a) -> m (GenStgStackClosure a) Исходный код

Traversable TyVarBndr Исходный код
Сведения об экземпляре

Определено в GHC.Internal.TH.Syntax

Методы

traverse :: Applicative f => (a -> f b) -> TyVarBndr a -> f (TyVarBndr b) Исходный код

sequenceA :: Applicative f => TyVarBndr (f a) -> f (TyVarBndr a) Исходный код

mapM :: Monad m => (a -> m b) -> TyVarBndr a -> m (TyVarBndr b) Исходный код

sequence :: Monad m => TyVarBndr (m a) -> m (TyVarBndr a) Исходный код

Traversable Maybe Исходный код

С версии: base-2.1

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Maybe a -> f (Maybe b) Исходный код

sequenceA :: Applicative f => Maybe (f a) -> f (Maybe a) Исходный код

mapM :: Monad m => (a -> m b) -> Maybe a -> m (Maybe b) Исходный код

sequence :: Monad m => Maybe (m a) -> m (Maybe a) Исходный код

Traversable Solo Исходный код

С версии: base-4.15

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Solo a -> f (Solo b) Исходный код

sequenceA :: Applicative f => Solo (f a) -> f (Solo a) Исходный код

mapM :: Monad m => (a -> m b) -> Solo a -> m (Solo b) Исходный код

sequence :: Monad m => Solo (m a) -> m (Solo a) Исходный код

Traversable [] Исходный код

С версии: base-2.1

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> [a] -> f [b] Исходный код

sequenceA :: Applicative f => [f a] -> f [a] Исходный код

mapM :: Monad m => (a -> m b) -> [a] -> m [b] Исходный код

sequence :: Monad m => [m a] -> m [a] Исходный код

Traversable (Arg a) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в Data.Semigroup

Методы

traverse :: Applicative f => (a0 -> f b) -> Arg a a0 -> f (Arg a b) Исходный код

sequenceA :: Applicative f => Arg a (f a0) -> f (Arg a a0) Исходный код

mapM :: Monad m => (a0 -> m b) -> Arg a a0 -> m (Arg a b) Исходный код

sequence :: Monad m => Arg a (m a0) -> m (Arg a a0) Исходный код

Ix i => Traversable (Array i) Исходный код

С версии: base-2.1

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Array i a -> f (Array i b) Исходный код

sequenceA :: Applicative f => Array i (f a) -> f (Array i a) Исходный код

mapM :: Monad m => (a -> m b) -> Array i a -> m (Array i b) Исходный код

sequence :: Monad m => Array i (m a) -> m (Array i a) Исходный код

Traversable (Either a) Исходный код

С версии: base-4.7.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a0 -> f b) -> Either a a0 -> f (Either a b) Исходный код

sequenceA :: Applicative f => Either a (f a0) -> f (Either a a0) Исходный код

mapM :: Monad m => (a0 -> m b) -> Either a a0 -> m (Either a b) Исходный код

sequence :: Monad m => Either a (m a0) -> m (Either a a0) Исходный код

Traversable (Proxy :: Type -> Type) Исходный код

С версии: base-4.7.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Proxy a -> f (Proxy b) Исходный код

sequenceA :: Applicative f => Proxy (f a) -> f (Proxy a) Исходный код

mapM :: Monad m => (a -> m b) -> Proxy a -> m (Proxy b) Исходный код

sequence :: Monad m => Proxy (m a) -> m (Proxy a) Исходный код

Traversable (U1 :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> U1 a -> f (U1 b) Исходный код

sequenceA :: Applicative f => U1 (f a) -> f (U1 a) Исходный код

mapM :: Monad m => (a -> m b) -> U1 a -> m (U1 b) Исходный код

sequence :: Monad m => U1 (m a) -> m (U1 a) Исходный код

Traversable (UAddr :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UAddr a -> f (UAddr b) Исходный код

sequenceA :: Applicative f => UAddr (f a) -> f (UAddr a) Исходный код

mapM :: Monad m => (a -> m b) -> UAddr a -> m (UAddr b) Исходный код

sequence :: Monad m => UAddr (m a) -> m (UAddr a) Исходный код

Traversable (UChar :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UChar a -> f (UChar b) Исходный код

sequenceA :: Applicative f => UChar (f a) -> f (UChar a) Исходный код

mapM :: Monad m => (a -> m b) -> UChar a -> m (UChar b) Исходный код

sequence :: Monad m => UChar (m a) -> m (UChar a) Исходный код

Traversable (UDouble :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UDouble a -> f (UDouble b) Исходный код

sequenceA :: Applicative f => UDouble (f a) -> f (UDouble a) Исходный код

mapM :: Monad m => (a -> m b) -> UDouble a -> m (UDouble b) Исходный код

sequence :: Monad m => UDouble (m a) -> m (UDouble a) Исходный код

Traversable (UFloat :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UFloat a -> f (UFloat b) Исходный код

sequenceA :: Applicative f => UFloat (f a) -> f (UFloat a) Исходный код

mapM :: Monad m => (a -> m b) -> UFloat a -> m (UFloat b) Исходный код

sequence :: Monad m => UFloat (m a) -> m (UFloat a) Исходный код

Traversable (UInt :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Сведения об экземпляре

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UInt a -> f (UInt b) Исходный код

sequenceA :: Applicative f => UInt (f a) -> f (UInt a) Исходный код

mapM :: Monad m => (a -> m b) -> UInt a -> m (UInt b) Исходный код

sequence :: Monad m => UInt (m a) -> m (UInt a) Исходный код

Traversable (UWord :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> UWord a -> f (UWord b) Исходный код

sequenceA :: Applicative f => UWord (f a) -> f (UWord a) Исходный код

mapM :: Monad m => (a -> m b) -> UWord a -> m (UWord b) Исходный код

sequence :: Monad m => UWord (m a) -> m (UWord a) Исходный код

Traversable (V1 :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> V1 a -> f (V1 b) Исходный код

sequenceA :: Applicative f => V1 (f a) -> f (V1 a) Исходный код

mapM :: Monad m => (a -> m b) -> V1 a -> m (V1 b) Исходный код

sequence :: Monad m => V1 (m a) -> m (V1 a) Исходный код

Traversable ((,) a) Исходный код

С версии: base-4.7.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a0 -> f b) -> (a, a0) -> f (a, b) Исходный код

sequenceA :: Applicative f => (a, f a0) -> f (a, a0) Исходный код

mapM :: Monad m => (a0 -> m b) -> (a, a0) -> m (a, b) Исходный код

sequence :: Monad m => (a, m a0) -> m (a, a0) Исходный код

Traversable (Const m :: Type -> Type) Исходный код

С версии: base-4.7.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> Const m a -> f (Const m b) Исходный код

sequenceA :: Applicative f => Const m (f a) -> f (Const m a) Исходный код

mapM :: Monad m0 => (a -> m0 b) -> Const m a -> m0 (Const m b) Исходный код

sequence :: Monad m0 => Const m (m0 a) -> m0 (Const m a) Исходный код

Traversable f => Traversable (Ap f) Исходный код

С версии: base-4.12.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Ap f a -> f0 (Ap f b) Исходный код

sequenceA :: Applicative f0 => Ap f (f0 a) -> f0 (Ap f a) Исходный код

mapM :: Monad m => (a -> m b) -> Ap f a -> m (Ap f b) Исходный код

sequence :: Monad m => Ap f (m a) -> m (Ap f a) Исходный код

Traversable f => Traversable (Alt f) Исходный код

С версии: base-4.12.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Alt f a -> f0 (Alt f b) Исходный код

sequenceA :: Applicative f0 => Alt f (f0 a) -> f0 (Alt f a) Исходный код

mapM :: Monad m => (a -> m b) -> Alt f a -> m (Alt f b) Исходный код

sequence :: Monad m => Alt f (m a) -> m (Alt f a) Исходный код

Traversable f => Traversable (Rec1 f) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Rec1 f a -> f0 (Rec1 f b) Исходный код

sequenceA :: Applicative f0 => Rec1 f (f0 a) -> f0 (Rec1 f a) Исходный код

mapM :: Monad m => (a -> m b) -> Rec1 f a -> m (Rec1 f b) Исходный код

sequence :: Monad m => Rec1 f (m a) -> m (Rec1 f a) Исходный код

(Traversable f, Traversable g) => Traversable (Product f g) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в Data.Functor.Product

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Product f g a -> f0 (Product f g b) Исходный код

sequenceA :: Applicative f0 => Product f g (f0 a) -> f0 (Product f g a) Исходный код

mapM :: Monad m => (a -> m b) -> Product f g a -> m (Product f g b) Исходный код

sequence :: Monad m => Product f g (m a) -> m (Product f g a) Исходный код

(Traversable f, Traversable g) => Traversable (Sum f g) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в Data.Functor.Sum

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Sum f g a -> f0 (Sum f g b) Исходный код

sequenceA :: Applicative f0 => Sum f g (f0 a) -> f0 (Sum f g a) Исходный код

mapM :: Monad m => (a -> m b) -> Sum f g a -> m (Sum f g b) Исходный код

sequence :: Monad m => Sum f g (m a) -> m (Sum f g a) Исходный код

(Traversable f, Traversable g) => Traversable (f :*: g) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> (f :*: g) a -> f0 ((f :*: g) b) Исходный код

sequenceA :: Applicative f0 => (f :*: g) (f0 a) -> f0 ((f :*: g) a) Исходный код

mapM :: Monad m => (a -> m b) -> (f :*: g) a -> m ((f :*: g) b) Исходный код

sequence :: Monad m => (f :*: g) (m a) -> m ((f :*: g) a) Исходный код

(Traversable f, Traversable g) => Traversable (f :+: g) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> (f :+: g) a -> f0 ((f :+: g) b) Исходный код

sequenceA :: Applicative f0 => (f :+: g) (f0 a) -> f0 ((f :+: g) a) Исходный код

mapM :: Monad m => (a -> m b) -> (f :+: g) a -> m ((f :+: g) b) Исходный код

sequence :: Monad m => (f :+: g) (m a) -> m ((f :+: g) a) Исходный код

Traversable (K1 i c :: Type -> Type) Исходный код

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f => (a -> f b) -> K1 i c a -> f (K1 i c b) Source

sequenceA :: Applicative f => K1 i c (f a) -> f (K1 i c a) Source

mapM :: Monad m => (a -> m b) -> K1 i c a -> m (K1 i c b) Source

sequence :: Monad m => K1 i c (m a) -> m (K1 i c a) Source

(Traversable f, Traversable g) => Traversable (Compose f g) Source

С версии: base-4.9.0.0

Детали экземпляра

Определено в Data.Functor.Compose

Методы

traverse :: Applicative f0 => (a -> f0 b) -> Compose f g a -> f0 (Compose f g b) Source

sequenceA :: Applicative f0 => Compose f g (f0 a) -> f0 (Compose f g a) Source

mapM :: Monad m => (a -> m b) -> Compose f g a -> m (Compose f g b) Source

sequence :: Monad m => Compose f g (m a) -> m (Compose f g a) Source

(Traversable f, Traversable g) => Traversable (f :.: g) Source

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> (f :.: g) a -> f0 ((f :.: g) b) Source

sequenceA :: Applicative f0 => (f :.: g) (f0 a) -> f0 ((f :.: g) a) Source

mapM :: Monad m => (a -> m b) -> (f :.: g) a -> m ((f :.: g) b) Source

sequence :: Monad m => (f :.: g) (m a) -> m ((f :.: g) a) Source

Traversable f => Traversable (M1 i c f) Source

С версии: base-4.9.0.0

Детали экземпляра

Определено в GHC.Internal.Data.Traversable

Методы

traverse :: Applicative f0 => (a -> f0 b) -> M1 i c f a -> f0 (M1 i c f b) Source

sequenceA :: Applicative f0 => M1 i c f (f0 a) -> f0 (M1 i c f a) Source

mapM :: Monad m => (a -> m b) -> M1 i c f a -> m (M1 i c f b) Source

sequence :: Monad m => M1 i c f (m a) -> m (M1 i c f a) Source

Вспомогательные функции

for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b) Source

for — это traverse с переставленными аргументами. Версию, игнорирующую результаты, см. в for_.

forM :: (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b) Source

forM — это mapM с переставленными аргументами. Версию, игнорирующую результаты, см. в forM_.

forAccumM :: (Monad m, Traversable t) => s -> t a -> (s -> a -> m (s, b)) -> m (s, t b) Source

forAccumM — это mapAccumM с переставленными аргументами.

С версии: base-4.18.0.0

mapAccumL :: Traversable t => (s -> a -> (s, b)) -> s -> t a -> (s, t b) Source

Функция mapAccumL ведет себя как комбинация fmap и foldl; она применяет функцию к каждому элементу структуры, передавая накапливаемый параметр слева направо, и возвращает конечное значение этого аккумулятора вместе с новой структурой.

Примеры
Развернуть

Базовое использование:

>>> mapAccumL (\a b -> (a + b, a)) 0 [1..10]
(55,[0,1,3,6,10,15,21,28,36,45])
>>> mapAccumL (\a b -> (a <> show b, a)) "0" [1..5]
("012345",["0","01","012","0123","01234"])

mapAccumR :: Traversable t => (s -> a -> (s, b)) -> s -> t a -> (s, t b) Source

Функция mapAccumR ведет себя как комбинация fmap и foldr; она применяет функцию к каждому элементу структуры, передавая накапливаемый параметр справа налево, и возвращает конечное значение этого аккумулятора вместе с новой структурой.

Примеры
Развернуть

Базовое использование:

>>> mapAccumR (\a b -> (a + b, a)) 0 [1..10]
(55,[54,52,49,45,40,34,27,19,10,0])
>>> mapAccumR (\a b -> (a <> show b, a)) "0" [1..5]
("054321",["05432","0543","054","05","0"])

mapAccumM :: (Monad m, Traversable t) => (s -> a -> m (s, b)) -> s -> t a -> m (s, t b) Source

Функция mapAccumM ведет себя как комбинация mapM и mapAccumL, которая обходит структуру, вычисляя действия и передавая накапливаемый параметр слева направо. Она возвращает конечное значение этого аккумулятора вместе с новой структурой. Аккумулятор часто используется для кэширования промежуточных результатов вычисления.

Примеры
Развернуть

Базовое использование:

>>> let expensiveDouble a = putStrLn ("Doubling " <> show a) >> pure (2 * a)
>>> :{
mapAccumM (\cache a -> case lookup a cache of
    Nothing -> expensiveDouble a >>= \double -> pure ((a, double):cache, double)
    Just double -> pure (cache, double)
    ) [] [1, 2, 3, 1, 2, 3]
:}
Doubling 1
Doubling 2
Doubling 3
([(3,6),(2,4),(1,2)],[2,4,6,2,4,6])

С версии: base-4.18.0.0

Общие определения для методов суперкласса

fmapDefault :: Traversable t => (a -> b) -> t a -> t b Source

Эта функция может использоваться в качестве значения для fmap в экземпляре Functor при условии, что определена traverse. (Использование fmapDefault с экземпляром Traversable, определенным только через sequenceA, приведет к бесконечной рекурсии.)

fmapDefault f ≡ runIdentity . traverse (Identity . f)

foldMapDefault :: (Traversable t, Monoid m) => (a -> m) -> t a -> m Source

Эта функция может использоваться в качестве значения для foldMap в экземпляре Foldable.

foldMapDefault f ≡ getConst . traverse (Const . f)

Обзор

Структуры Traversable поддерживают поэлементное упорядочивание эффектов Applicative (и, следовательно, также эффектов Monad) для построения новых структур той же формы, что и исходная.

Чтобы проиллюстрировать, что понимается под той же формой: если входная структура представляет собой [a], каждая выходная структура представляет собой список [b] той же длины, что и входная. Если на входе Tree a, каждый выход Tree b имеет тот же граф промежуточных узлов и листьев. Аналогично, если на входе 2-кортеж (x, a), каждым выходом будет 2-кортеж (x, b) и так далее.

На самом деле возможно декомпозировать обходимую структуру t a на ее форму (также называемую spine) типа t () и список ее элементов [a]. Исходную структуру можно точно восстановить из ее основы (spine) и списка элементов.

Реализация экземпляра Traversable для заданной структуры естественным образом следует из ее типа; подробности см. в разделе Конструирование. Экземпляры должны удовлетворять законам, перечисленным в разделе «Законы». Разнообразие вариантов применения структур Traversable проистекает из множества возможных вариантов аппликативных эффектов. Некоторые примеры см. в разделе Расширенные обходы.

Каждая структура Traversable является одновременно и Functor, и Foldable, поскольку необходимые экземпляры можно реализовать через traverse, используя fmapDefault для fmap и foldMapDefault для foldMap. Прямые оптимизированные реализации этих методов суперкласса в некоторых случаях могут быть более эффективными.

Методы traverse и mapM

Для аппликативного функтора f (Applicative) и обходимого функтора t (Traversable) сигнатуры типов traverse и fmap весьма похожи:

fmap     :: (a -> f b) -> t a -> t (f b)
traverse :: (a -> f b) -> t a -> f (t b)

Ключевое отличие заключается в том, что fmap создает структуру, чьи элементы (типа f b) являются отдельными эффектами, тогда как traverse создает совокупный эффект, возвращающий структуры типа t b.

Например, когда f — это монада IO, а t — это List, fmap возвращает список действий IO, тогда как traverse конструирует действие IO, результатом вычисления которого является список возвращаемых значений отдельных действий, выполненных слева направо.

traverse :: (a -> IO b) -> [a] -> IO [b]

Функция mapM является специализацией traverse для случая, когда f — это Monad. Для монад mapM более идиоматична, чем traverse. В остальном они в целом идентичны (хотя mapM может быть специально оптимизирована для монад и работать эффективнее, чем более общая traverse).

traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
mapM     :: (Monad       m, Traversable t) => (a -> m b) -> t a -> m (t b)

Когда обходимый терм представляет собой простую переменную или выражение, а запускаемое монадическое действие является нетривиальным блоком do, более естественно писать действие последним. Эта идиома поддерживается функциями for, forM и forAccumM, которые представляют собой версии с переставленными аргументами для traverse, mapM и mapAccumM соответственно.

Их аналоги из Foldable (только для эффектов)

Методы traverse и mapM имеют аналоги в модуле Data.Foldable. Это traverse_ и mapM_, а также их варианты с переставленными аргументами for_ и forM_ соответственно. Тип результата — f (), они не возвращают обновленную структуру и могут использоваться для последовательного выполнения эффектов над всеми элементами структуры Traversable (любой Foldable) исключительно ради их побочных эффектов.

Если структура Traversable пуста, результатом является pure (). При возникновении короткого замыкания эффектов результат f () может, например, быть Nothing, если f — это Maybe, или Left e, когда это Either e.

Возможно, стоит отметить, что Maybe является не только потенциальным функтором Applicative для возвращаемого значения первого аргумента traverse, но и сам по себе является структурой Traversable с нулем или одним элементом. Удобная идиома для условного выполнения действия исключительно ради его эффектов над значением Just (и ничегонеделания в противном случае):

-- action :: Monad m => a -> m ()
-- mvalue :: Maybe a
mapM_ action mvalue -- :: m ()

что более кратко, чем:

maybe (return ()) action mvalue

Идиома mapM_ работает без изменений, если тип mvalue позже рефакторится из Maybe a в Either e a (при условии, что по-прежнему допустимо ничего не делать в случае Left).

Кратность результата

Когда traverse или mapM применяется к пустой структуре ts (такой, для которой null ts равен True), возвращаемым значением является pure ts независимо от переданной функции g :: a -> f b. Применить функцию невозможно, поскольку нет значений типа a, однако ее тип определяет соответствующий экземпляр pure.

null ts ==> traverse g ts == pure ts

В противном случае, когда ts не пуста и хотя бы одно значение типа b получается из каждого f a, структуры t b имеют ту же форму (длина списка, граф узлов дерева, ...), что и входная структура t a, однако позиции, ранее занимаемые элементами типа a, теперь содержат элементы типа b.

Один обход может порождать одну, ноль или множество таких структур. Случай с нулем возникает, когда один из эффектов f a, упорядоченных в процессе обхода, не возвращает значений для замены. Случай со многими структурами возникает, когда один из упорядоченных эффектов возвращает несколько значений.

Функция traverse не выполняет выборочную фильтрацию элементов в результирующей структуре, в отличие от, например, mapMaybe.

>>> let incOdd n = if odd n then Just $ n + 1 else Nothing
>>> mapMaybe incOdd [1, 2, 3]
[2,4]
>>> traverse incOdd [1, 3, 5]
Just [2,4,6]
>>> traverse incOdd [1, 2, 3]
Nothing

В приведенных выше примерах с Maybe в качестве Applicative f мы видим, что количество структур t b, созданных с помощью traverse, может отличаться от единицы: оно равно нулю, когда результат по короткому замыканию превращается в Nothing. То же самое может произойти, когда f — это List и результат равен [], или f — это Either e и результат равен Left (x :: e), или, возможно, значение empty некоторого функтора Alternative.

Когда f — это, например, List, и отображение g :: a -> [b] возвращает более одного значения для некоторых входных данных a (и хотя бы одно для всех a), результат mapM g ts будет содержать несколько структур той же формы, что и ts:

List.length (mapM g ts) == List.product (fmap (List.length . g) ts)

Например:

>>> List.length $ mapM (\n -> [1..n]) [1..6]
720
>>> List.product $ List.length . (\n -> [1..n]) <$> [1..6]
720

Иными словами, обход с функцией g :: a -> [b] по входной структуре t a возвращает список [t b], длина которого равна произведению длин списков, возвращаемых функцией g для каждого элемента входной структуры! Отдельные элементы a структуры поочередно заменяются каждым элементом из g a:

>>> mapM (\n -> [1..n]) $ Just 3
[Just 1,Just 2,Just 3]
>>> mapM (\n -> [1..n]) [1..3]
[[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3]]

Если какой-либо элемент структуры t a отображается функцией g в пустой список, то весь совокупный результат пуст, поскольку отсутствует значение для заполнения одной из позиций выходной структуры:

>>> mapM (\n -> [1..n]) $ [0..6] -- [1..0] is empty
[]

Методы sequenceA и sequence

Методы sequenceA и sequence полезны, когда у вас есть контейнер отложенных аппликативных или монадических эффектов, и вы хотите объединить их в один эффект, производящий ноль или более контейнеров с вычисленными значениями.

sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
sequence  :: (Monad       m, Traversable t) => t (m a) -> m (t a)
sequenceA = traverse id -- default definition
sequence  = sequenceA   -- default definition

Когда монада m является IO, применение sequence к списку действий IO выполняет каждое из них по очереди, возвращая список результатов:

sequence [putStr "Hello ", putStrLn "World!"]
    = (\a b -> [a,b]) <$> putStr "Hello " <*> putStrLn "World!"
    = do u1 <- putStr "Hello "
         u2 <- putStrLn "World!"
         return [u1, u2]         -- In this case  [(), ()]

Для sequenceA недетерминированное поведение List проще всего увидеть на примере списка списков (элементов некоторого общего фиксированного типа). Результатом является декартово произведение всех подсписков:

>>> sequenceA [[0, 1, 2], [30, 40], [500]]
[[0,30,500],[0,40,500],[1,30,500],[1,40,500],[2,30,500],[2,40,500]]

Поскольку входной список состоит из трех элементов (подсписков), результатом является список троек (той же формы).

Осторожность с реализациями методов по умолчанию

Метод traverse имеет реализацию по умолчанию через sequenceA:

traverse g = sequenceA . fmap g

но полагаться на эту реализацию по умолчанию не рекомендуется — для этого требуется, чтобы структура уже независимо являлась Functor. Определение sequenceA через traverse id намного проще, чем traverse, выраженная через композицию sequenceA и fmap. Экземпляры, как правило, должны реализовывать traverse явно. В некоторых случаях также имеет смысл реализовать специализированную функцию mapM.

Поскольку fmapDefault определена через traverse (чье определение по умолчанию через sequenceA использует fmap), нельзя использовать fmapDefault для определения экземпляра Functor, если экземпляр Traversable напрямую определяет только sequenceA.

Монадические короткие замыкания

Когда монада m — это Either или Maybe (более обобщенно — любой MonadPlus), рассматриваемый эффект заключается в преждевременном прерывании (коротком замыкании) результата при обнаружении Left или Nothing (в более общем случае — mzero).

>>> sequence [Just 1,Just 2,Just 3]
Just [1,2,3]
>>> sequence [Just 1,Nothing,Just 3]
Nothing
>>> sequence [Right 1,Right 2,Right 3]
Right [1,2,3]
>>> sequence [Right 1,Left "sorry",Right 3]
Left "sorry"

Результат sequence работает по принципу «всё или ничего»: либо структуры в точности той же формы, что и входные данные, либо вовсе ничего. Функция sequence не выполняет выборочную фильтрацию, в отличие от, например, catMaybes или rights:

>>> catMaybes [Just 1,Nothing,Just 3]
[1,3]
>>> rights [Right 1,Left "sorry",Right 3]
[1,3]

Пример экземпляра для бинарного дерева

Определение экземпляра Traversable для бинарного дерева очень похоже на соответствующий экземпляр Functor для заданного типа данных:

data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)

каноническим экземпляром Functor будет

instance Functor Tree where
   fmap g Empty        = Empty
   fmap g (Leaf x)     = Leaf (g x)
   fmap g (Node l k r) = Node (fmap g l) (g k) (fmap g r)

каноническим экземпляром Traversable будет

instance Traversable Tree where
   traverse g Empty        = pure Empty
   traverse g (Leaf x)     = Leaf <$> g x
   traverse g (Node l k r) = Node <$> traverse g l <*> g k <*> traverse g r

Это определение работает для любого g :: a -> f b, где f является аппликативным функтором, поскольку законы для (<*>) подразумевают требуемую ассоциативность.

При желании мы можем добавить явный невиртуальный (non-default) метод mapM:

   mapM g Empty        = return Empty
   mapM g (Leaf x)     = Leaf <$> g x
   mapM g (Node l k r) = do
       ml <- mapM g l
       mk <- g k
       mr <- mapM g r
       return $ Node ml mk mr

См. раздел Конструирование ниже для более подробного рассмотрения общего случая, однако, как упоминалось в Обзоре выше, определения экземпляров обычно довольно просты, и все интересное поведение является результатом интересного выбора функтора Applicative для обхода.

Прямой (pre-order) и обратный (post-order) обход дерева

Возможно, стоит отметить, что определенный выше обход дает центрированную (in-order) последовательность элементов. Если же вам требуется последовательность в прямом порядке (pre-order) (сначала родительский узел, затем дочерние) или в обратном порядке (post-order) (сначала дочерние узлы, затем родительский), вы можете определить экземпляр соответствующим образом:

inOrderNode :: Tree a -> a -> Tree a -> Tree a
inOrderNode l x r = Node l x r

preOrderNode :: a -> Tree a -> Tree a -> Tree a
preOrderNode x l r = Node l x r

postOrderNode :: Tree a -> Tree a -> a -> Tree a
postOrderNode l r x = Node l x r

-- Traversable instance with in-order traversal
instance Traversable Tree where
    traverse g t = case t of
        Empty      -> pure Empty
        Leaf x     -> Leaf <$> g x
        Node l x r -> inOrderNode <$> traverse g l <*> g x <*> traverse g r

-- Traversable instance with pre-order traversal
instance Traversable Tree where
    traverse g t = case t of
        Empty      -> pure Empty
        Leaf x     -> Leaf <$> g x
        Node l x r -> preOrderNode <$> g x <*> traverse g l <*> traverse g r

-- Traversable instance with post-order traversal
instance Traversable Tree where
    traverse g t = case t of
        Empty      -> pure Empty
        Leaf x     -> Leaf <$> g x
        Node l x r -> postOrderNode <$> traverse g l <*> traverse g r <*> g x

Поскольку во всех трех случаях используется одна и та же базовая структура Tree, можно использовать обертки newtype, чтобы сделать доступными все три варианта одновременно! Пользователю достаточно лишь обернуть корень дерева в соответствующий newtype для желаемого порядка обхода. Соответствующие определения экземпляров показаны ниже (см. раздел приведение (coercion), если вы не знакомы с использованием coerce в примерах кода):

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}

-- Default in-order traversal

import Data.Coerce (coerce)
import Data.Traversable

data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
instance Functor  Tree where fmap    = fmapDefault
instance Foldable Tree where foldMap = foldMapDefault

instance Traversable Tree where
    traverse _ Empty = pure Empty
    traverse g (Leaf a) = Leaf <$> g a
    traverse g (Node l a r) = Node <$> traverse g l <*> g a <*> traverse g r

-- Optional pre-order traversal

newtype PreOrderTree a = PreOrderTree (Tree a)
instance Functor  PreOrderTree where fmap    = fmapDefault
instance Foldable PreOrderTree where foldMap = foldMapDefault

instance Traversable PreOrderTree where
    traverse _ (PreOrderTree Empty)        = pure $ preOrderEmpty
    traverse g (PreOrderTree (Leaf x))     = preOrderLeaf <$> g x
    traverse g (PreOrderTree (Node l x r)) = preOrderNode
        <$> g x
        <*> traverse g (coerce l)
        <*> traverse g (coerce r)

preOrderEmpty :: forall a. PreOrderTree a
preOrderEmpty = coerce (Empty @a)
preOrderLeaf :: forall a. a -> PreOrderTree a
preOrderLeaf = coerce (Leaf @a)
preOrderNode :: a -> PreOrderTree a -> PreOrderTree a -> PreOrderTree a
preOrderNode x l r = coerce (Node (coerce l) x (coerce r))

-- Optional post-order traversal

newtype PostOrderTree a = PostOrderTree (Tree a)
instance Functor  PostOrderTree where fmap    = fmapDefault
instance Foldable PostOrderTree where foldMap = foldMapDefault

instance Traversable PostOrderTree where
    traverse _ (PostOrderTree Empty)        = pure postOrderEmpty
    traverse g (PostOrderTree (Leaf x))     = postOrderLeaf <$> g x
    traverse g (PostOrderTree (Node l x r)) = postOrderNode
        <$> traverse g (coerce l)
        <*> traverse g (coerce r)
        <*> g x

postOrderEmpty :: forall a. PostOrderTree a
postOrderEmpty = coerce (Empty @a)
postOrderLeaf :: forall a. a -> PostOrderTree a
postOrderLeaf = coerce (Leaf @a)
postOrderNode :: PostOrderTree a -> PostOrderTree a -> a -> PostOrderTree a
postOrderNode l r x = coerce (Node (coerce l) x (coerce r))

С учетом вышесказанного, для примера дерева:

inOrder :: Tree Int
inOrder = Node (Node (Leaf 10) 3 (Leaf 20)) 5 (Leaf 42)

мы получаем:

import Data.Foldable (toList)
print $ toList inOrder
[10,3,20,5,42]

print $ toList (coerce inOrder :: PreOrderTree Int)
[5,3,10,20,42]

print $ toList (coerce inOrder :: PostOrderTree Int)
[10,20,3,42,5]

Обычно также определяются экземпляры для дополнительных распространенных классов типов, таких как Eq, Ord, Show и т. д.

Интуитивное понимание конструирования

Чтобы иметь возможность рассуждать о том, как эффекты заданного типа Applicative будут упорядочиваться в обобщенной структуре Traversable с помощью ее метода traversable и связанных с ним методов, полезно более внимательно изучить реализацию обобщенного метода traverse. Мы рассмотрим, как строятся обобщенные обходы, главным образом для того, чтобы уметь предсказывать их поведение с точки зрения пользователя, даже если вы не определяете собственные экземпляры Traversable.

Обходимые структуры t a собираются инкрементально из своих составных частей, например путем добавления отдельных элементов типа a в начало или в конец либо, в более общем случае, путем рекурсивного комбинирования меньших составных обходимых блоков, содержащих несколько таких элементов.

Как и в примере с деревом выше, объединяемые компоненты обычно собираются вместе подходящим конструктором, то есть функцией, принимающей два или более аргументов и возвращающей составное значение.

Метод traverse дополняет простое инкрементальное конструирование протягиванием эффектов Applicative некоторой функции g :: a -> f b.

Базовыми строительными блоками, которые мы будем использовать для моделирования конструирования traverse, является гипотетический набор элементарных функций, некоторые из которых могут иметь прямые аналоги в конкретных структурах Traversable. Например, конструктор (:) является аналогом prepend для списков или более общего combine.

empty :: t a               -- build an empty container
singleton :: a -> t a      -- build a one-element container
prepend :: a -> t a -> t a -- extend by prepending a new initial element
append  :: t a -> a -> t a -- extend by appending a new final element
combine :: a1 -> a2 -> ... -> an -> t a -- combine multiple inputs
  • Пустая структура не содержит элементов типа a, поэтому применять g не к чему, но поскольку нам нужен результат типа f (t b), мы просто используем экземпляр pure для f, чтобы обернуть пустое значение типа t b:

    traverse _ (empty :: t a) = pure (empty :: t b)

    Для монады List значением empty является [], тогда как для Maybe это Nothing. Для Either e a мы имеем случай empty для каждого значения e:

    traverse _ (Left e :: Either e a) = pure $ (Left e :: Either e b)
  • Одноэлементная структура содержит только один элемент типа a, и traverse может взять этот a, применить g :: a -> f b, получив f b, а затем применить к нему fmap singleton, получив требуемый f (t b):

    traverse g (singleton a) = fmap singleton $ g a

    Обратите внимание, что если f — это List, а g возвращает несколько значений, результатом будет список из нескольких одноэлементных значений t b!

    Поскольку Maybe и Either являются либо пустыми, либо одноэлементными, мы получаем

    traverse _ Nothing = pure Nothing
    traverse g (Just a) = Just <$> g a
    traverse _ (Left e) = pure (Left e)
    traverse g (Right a) = Right <$> g a

    Для List значение empty — это [], а singleton — это (:[]), поэтому мы имеем:

    traverse _ []  = pure []
    traverse g [a] = fmap (:[]) (g a)
                   = (:) <$> (g a) <*> traverse g []
                   = liftA2 (:) (g a) (traverse g [])
  • Когда структура строится путем добавления еще одного элемента через prepend или append, обход сводится к:

    traverse g (prepend a t0) = prepend <$> (g a) <*> traverse g t0
                              = liftA2 prepend (g a) (traverse g t0)
    traverse g (append t0 a) = append <$> traverse g t0 <*> g a
                             = liftA2 append (traverse g t0) (g a)

    Происхождение комбинаторного произведения в случае, когда f — это List, теперь должно быть очевидным: когда traverse g t0 содержит n элементов, а g a содержит m элементов, недетерминированный экземпляр Applicative для List даст результат с m * n элементами.

  • При объединении более крупных блоков мы снова используем (<*>) для комбинирования обходов компонентов. При этом простые элементы a отображаются в f b через g, а составные обходимые подструктуры преобразуются через traverse g:

    traverse g (combine a1 a2 ... an) =
        combine <$> t1 <*> t2 <*> ... <*> tn
      where
         t1 = g a1          -- if a1 fills a slot of type @a@
            = traverse g a1 -- if a1 is a traversable substructure
         ... ditto for the remaining constructor arguments ...

Приведенные выше определения упорядочивают эффекты Applicative функции f в ожидаемом порядке, формируя результат ожидаемой формы t.

Для списков это принимает вид:

traverse g [] = pure []
traverse g (x:xs) = liftA2 (:) (g a) (traverse g xs)

Фактическое определение traverse для списков представляет собой эквивалентную правую свертку для облегчения слияния списков.

traverse g = foldr (\x r -> liftA2 (:) (g x) r) (pure [])

Продвинутые обходы

В разделах ниже мы рассмотрим некоторые продвинутые варианты эффектов Applicative, приводящие к совершенно разным трансформациям структур Traversable.

Эти примеры охватывают реализации функций fmapDefault, foldMapDefault, mapAccumL и mapAccumR, иллюстрируя использование Identity, Const и эффектов Applicative с состоянием. Пример с ZipList иллюстрирует использование менее известного экземпляра Applicative для списков.

Это дополнительный материал, который не является обязательным для базового понимания структур Traversable. Если вы впервые знакомитесь со структурами Traversable, вы можете вернуться к нему позже.

Приведение типов (coercion)

В некоторых примерах используется продвинутая возможность Haskell, а именно приведение типов (coercion) с помощью newtype. Это сделано по двум причинам:

  • Использование coerce позволяет избежать загромождения кода функциями, оборачивающими и разворачивающими термы newtype, которые во время выполнения неотличимы от базового значения. Приведение типов особенно удобно, когда в противном случае пришлось бы применять несколько конструкторов newtype к аргументам функции, а затем снимать несколько слоев тех же конструкторов с результата функции.
  • Использование coerce позволяет генерировать более эффективный код за счет повторного использования исходного значения вместо выделения памяти для обернутого клона.

Если вы не знакомы с coerce, не беспокойтесь: это просто сокращение, которое, например, при наличии:

newtype Foo a = MkFoo { getFoo :: a }
newtype Bar a = MkBar { getBar :: a }
newtype Baz a = MkBaz { getBaz :: a }
f :: Baz Int -> Bar (Foo String)

позволяет написать:

x :: Int -> String
x = coerce f

вместо

x = getFoo . getBar . f . MkBaz

Identity: функция fmapDefault

Простейшим аппликативным функтором является Identity, который просто оборачивает и разворачивает чистые значения и применение функций. Это позволяет нам определить fmapDefault:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
import Data.Coercible (coerce)

fmapDefault :: forall t a b. Traversable t => (a -> b) -> t a -> t b
fmapDefault = coerce (traverse @t @Identity @a @b)

Использование приведения устраняет необходимость явного оборачивания и разворачивания термов с помощью Identity и runIdentity.

Как отмечалось в Обзоре, функцию fmapDefault можно использовать для определения необходимого экземпляра Functor структуры Traversable только тогда, когда метод traverse реализован явно. Если бы кроме этого метод traverse определялся через sequenceA и fmap, это привело бы к бесконечному циклу.

State: функции mapAccumL и mapAccumR

Аппликативные функторы, протягивающие изменяющееся состояние через вычисление, представляют собой интересный сценарий использования traverse. Функции mapAccumL и mapAccumR в этом модуле определены через подобные обходы.

Сначала мы определим упрощенную (не являющуюся трансформером монад) версию State, которая протягивает состояние s по цепочке вычислений слева направо. Ее оператор (<*>) передает входное состояние сначала левому аргументу, а затем результирующее состояние передается правому аргументу, который возвращает конечное состояние.

newtype StateL s a = StateL { runStateL :: s -> (s, a) }

instance Functor (StateL s) where
    fmap f (StateL kx) = StateL $ \ s ->
        let (s', x) = kx s in (s', f x)

instance Applicative (StateL s) where
    pure a = StateL $ \s -> (s, a)
    (StateL kf) <*> (StateL kx) = StateL $ \ s ->
        let { (s',  f) = kf s
            ; (s'', x) = kx s' } in (s'', f x)
    liftA2 f (StateL kx) (StateL ky) = StateL $ \ s ->
        let { (s',  x) = kx s
            ; (s'', y) = ky s' } in (s'', f x y)

С помощью StateL мы можем определить mapAccumL следующим образом:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
mapAccumL :: forall t s a b. Traversable t
          => (s -> a -> (s, b)) -> s -> t a -> (s, t b)
mapAccumL g s ts = coerce (traverse @t @(StateL s) @a @b) (flip g) ts s

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы newtype.

Тип flip g может быть приведен к a -> StateL b, что делает его пригодным для использования с traverse. В рамках аппликативного конструирования StateL (t b) обновления состояния будут протягиваться слева направо вдоль последовательности элементов t a.

Хотя mapAccumR имеет сигнатуру типа, идентичную mapAccumL, она отличается ожидаемым порядком вычисления эффектов, который должен происходить справа налево.

Для этого нам понадобится модифицированная структура управления StateR, которая протягивает состояние справа налево, передавая входное состояние своему правому аргументу, а затем используя полученное состояние в качестве входного для левого аргумента:

newtype StateR s a = StateR { runStateR :: s -> (s, a) }

instance Functor (StateR s) where
    fmap f (StateR kx) = StateR $ \s ->
        let (s', x) = kx s in (s', f x)

instance Applicative (StateR s) where
    pure a = StateR $ \s -> (s, a)
    (StateR kf) <*> (StateR kx) = StateR $ \ s ->
        let { (s',  x) = kx s
            ; (s'', f) = kf s' } in (s'', f x)
    liftA2 f (StateR kx) (StateR ky) = StateR $ \ s ->
        let { (s',  y) = ky s
            ; (s'', x) = kx s' } in (s'', f x y)

С помощью StateR мы можем определить mapAccumR следующим образом:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
mapAccumR :: forall t s a b. Traversable t
          => (s -> a -> (s, b)) -> s -> t a -> (s, t b)
mapAccumR g s0 ts = coerce (traverse @t @(StateR s) @a @b) (flip g) ts s0

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы newtype.

Различные обходы с состоянием могут быть построены из mapAccumL и mapAccumR при подходящем выборе g или созданы напрямую аналогичным образом.

Const: функция foldMapDefault

Функтор Const делает возможным применение traverse для свертывания входной структуры в результирующее значение без конструирования каких-либо выходных значений того же типа или формы.

Как отмечалось выше, ограничение суперкласса Foldable оправдано тем, что из traverse можно построить foldMap, foldr и т. д. Используемый здесь метод полезен сам по себе и рассматривается ниже.

Ключевая особенность сверток заключается в том, что они могут редуцировать входную структуру до итогового значения. Часто ни сама входная структура, ни ее видоизмененная копия больше не нужны после вычисления свертки, а благодаря слиянию списков входные данные могут даже не находиться в памяти целиком одновременно.

На первый взгляд метод traverse не кажется подходящим строительным блоком для сверток, поскольку его возвращаемое значение f (t b), по-видимому, сохраняет измененные копии входной структуры. Но присутствие t b в сигнатуре типа не обязательно означает, что термы типа t b действительно встроены в f (t b). Простейший способ исключить лишние термы — основать аппликативный функтор, используемый с traverse, на Const.

Мало того, что Const a b содержит только значение a, а параметр b является всего лишь фантомным типом, так еще и когда для m существует экземпляр Monoid, Const m является функтором Applicative:

import Data.Coerce (coerce)
newtype Const a b = Const { getConst :: a } deriving (Eq, Ord, Show) -- etc.
instance Functor (Const m) where fmap = const coerce
instance Monoid m => Applicative (Const m) where
   pure _   = Const mempty
   (<*>)    = coerce (mappend :: m -> m -> m)
   liftA2 _ = coerce (mappend :: m -> m -> m)

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы newtype.

Таким образом, мы можем определить специализацию traverse:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
traverseC :: forall t a m. (Monoid m, Traversable t)
          => (a -> Const m ()) -> t a -> Const m (t ())
traverseC = traverse @t @(Const m) @a @()

Для которой аппликативное конструирование traverse приводит к:

null ts ==> traverseC g ts = Const mempty
traverseC g (prepend x xs) = Const (g x) <> traverseC g xs

Иными словами, это позволяет определить:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
foldMapDefault :: forall t a m. (Monoid m, Traversable t) => (a -> m) -> t a -> m
foldMapDefault = coerce (traverse @t @(Const m) @a @())

Чего достаточно для определения экземпляра суперкласса Foldable:

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы newtype.

instance Traversable t => Foldable t where foldMap = foldMapDefault

Однако может быть полезно также напрямую определить варианты реализаций по умолчанию для foldr и foldl', построение которых требует чуть более сложного механизма:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
import Data.Coerce (coerce)
import Data.Functor.Const (Const(..))
import Data.Semigroup (Dual(..), Endo(..))
import GHC.Exts (oneShot)

foldrDefault :: forall t a b. Traversable t
             => (a -> b -> b) -> b -> t a -> b
foldrDefault f z = \t ->
    coerce (traverse @t @(Const (Endo b)) @a @()) f t z

foldlDefault' :: forall t a b. Traversable t => (b -> a -> b) -> b -> t a -> b
foldlDefault' f z = \t ->
    coerce (traverse @t @(Const (Dual (Endo b))) @a @()) f' t z
  where
    f' :: a -> b -> b
    f' a = oneShot $ \ b -> b `seq` f b a

В приведенном выше коде мы используем Endo b Monoid и его Dual для композиции последовательности обновлений аккумулятора b -> b в порядке либо слева направо, либо справа налево.

Использование seq в определении foldlDefault' обеспечивает строгость аккумулятора.

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы newtype.

Функция oneShot дает компилятору подсказку, способствующую правильной оптимизации лямбда-термов, которые вызываются не более одного раза (для каждого элемента a), поэтому ему не следует пытаться предварительно вычислять и повторно использовать подвыражения, окупающиеся только при многократном выполнении. В остальном это просто функция тождества.

ZipList: транспонирование списков списков

В качестве разминки перед рассмотрением аппликативного функтора ZipList Applicative мы сначала рассмотрим более простой аналог. Сначала определим тип 2-элементного вектора фиксированной длины Vec2, чей экземпляр Applicative объединяет пару функций с парой значений путем применения каждой функции к соответствующей позиции значения:

data Vec2 a = Vec2 a a
instance Functor Vec2 where
    fmap f (Vec2 a b) = Vec2 (f a) (f b)
instance Applicative Vec2 where
    pure x = Vec2 x x
    liftA2 f (Vec2 a b) (Vec2 p q) = Vec2 (f a p) (f b q)
instance Foldable Vec2 where
    foldr f z (Vec2 a b) = f a (f b z)
    foldMap f (Vec2 a b) = f a <> f b
instance Traversable Vec2 where
    traverse f (Vec2 a b) = Vec2 <$> f a <*> f b

Наряду с аналогичным определением для 3-элементных векторов фиксированной длины:

data Vec3 a = Vec3 a a a
instance Functor Vec3 where
    fmap f (Vec3 x y z) = Vec3 (f x) (f y) (f z)
instance Applicative Vec3 where
    pure x = Vec3 x x x
    liftA2 f (Vec3 p q r) (Vec3 x y z) = Vec3 (f p x) (f q y) (f r z)
instance Foldable Vec3 where
    foldr f z (Vec3 a b c) = f a (f b (f c z))
    foldMap f (Vec3 a b c) = f a <> f b <> f c
instance Traversable Vec3 where
    traverse f (Vec3 a b c) = Vec3 <$> f a <*> f b <*> f c

С приведенными выше определениями функция sequenceA (как и traverse id) действует как операция транспонирования матрицы на Vec2 (Vec3 Int), давая соответствующий Vec3 (Vec2 Int):

Пусть t = Vec2 (Vec3 1 2 3) (Vec3 4 5 6) — наша структура Traversable, а g = id :: Vec3 Int -> Vec3 Int — функция, используемая для обхода t. Тогда мы имеем:

traverse g t = Vec2 <$> (Vec3 1 2 3) <*> (Vec3 4 5 6)
             = Vec3 (Vec2 1 4) (Vec2 2 5) (Vec2 3 6)

Эту конструкцию можно обобщить с векторов фиксированной длины на списки переменной длины с помощью ZipList. Это дает операцию транспонирования, которая хорошо работает для списков равной длины. Если некоторые из списков длиннее других, они усекаются до наибольшей общей длины.

Мы уже рассматривали стандартный экземпляр Applicative для List, для которого применение m функций f1, f2, ..., fm к n входным значениям a1, a2, ..., an дает m * n результатов:

>>> :set -XTupleSections
>>> [("f1",), ("f2",), ("f3",)] <*> [1,2]
[("f1",1),("f1",2),("f2",1),("f2",2),("f3",1),("f3",2)]

Однако существуют еще два распространенных способа превратить списки в управляющие структуры Applicative. Первый — через Const [a], поскольку списки являются моноидами относительно конкатенации, а мы уже видели, что Const m является функтором Applicative, когда m — это Monoid. Второй основан на zipWith и называется ZipList:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype ZipList a = ZipList { getZipList :: [a] }
    deriving (Show, Eq, ..., Functor)

instance Applicative ZipList where
    liftA2 f (ZipList xs) (ZipList ys) = ZipList $ zipWith f xs ys
    pure x = repeat x

Определение liftA2 достаточно понятно: вместо применения f к каждой паре (x, y), независимо выбираемой из xs и ys, используются только соответствующие пары по каждому индексу в двух списках.

Определение pure может показаться неожиданным, но оно необходимо для соблюдения законов экземпляра:

liftA2 f (pure x) ys == fmap (f x) ys

Поскольку ys может иметь любую длину, нам необходимо предоставить бесконечный запас значений x в pure x, чтобы иметь значение для сопоставления в пару с каждым элементом y.

Когда ZipList выступает в роли функтора Applicative, используемого при конструировании обхода, ZipList, содержащий частично построенную структуру с m элементами, объединяется с компонентом, содержащим n элементов, через zipWith, в результате чего получается min m n результатов!

Следовательно, traverse с g :: a -> ZipList b сформирует ZipList структур t b, количество элементов в которых равно минимальной длине списков ZipList g a, где a пробегает элементы t. Когда t пусто, длина бесконечна (как и ожидается для минимума пустого множества).

Если структура t содержит значения типа ZipList a, мы можем использовать функцию тождества id :: ZipList a -> ZipList a в качестве первого аргумента traverse:

traverse (id :: ZipList a -> ZipList a) :: t (ZipList a) -> ZipList (t a)

Количество элементов в результирующем ZipList будет равно длине кратчайшего элемента-ZipList в t. Каждый выходной t a будет иметь ту же форму, что и входной t (ZipList a), то есть содержать то же количество элементов.

Если представить элементы t (ZipList a) как его строки, а элементы каждого отдельного ZipList как столбцы этой строки, мы увидим, что наш обход реализует операцию транспонирования, меняющую местами строки и столбцы t после предварительного усечения всех строк до количества столбцов в самой короткой из них.

Поскольку на самом деле traverse id — это просто sequenceA, вышесказанное сводится к весьма лаконичному определению функции transpose, где приведение используется для неявного оборачивания и разворачивания ZipList newtype по мере необходимости, давая функцию, работающую со списком списков:

>>> :set -XScopedTypeVariables
>>> import Control.Applicative (ZipList(..))
>>> import Data.Coerce (coerce)
>>> >>> :{
>>> let
>>>     transpose :: forall a. [[a]] -> [[a]]
>>>     transpose = coerce (sequenceA :: [ZipList a] -> ZipList [a])
>>> in transpose [[1,2,3],[4..],[7..]]
>>> :}
[[1,4,7],[2,5,8],[3,6,9]]

Использование приведения устраняет необходимость явно оборачивать и разворачивать термы ZipList.

Законы

Определение traverse должно удовлетворять следующим законам:

Естественность
t . traverse f = traverse (t . f) для каждого аппликативного преобразования t
Тождественность
traverse Identity = Identity
Композиция
traverse (Compose . fmap g . f) = Compose . fmap (traverse g) . traverse f

Определение sequenceA должно удовлетворять следующим законам:

Естественность
t . sequenceA = sequenceA . fmap t для каждого аппликативного преобразования t
Тождественность
sequenceA . fmap Identity = Identity
Композиция
sequenceA . fmap Compose = Compose . fmap sequenceA . sequenceA

где аппликативное преобразование — это функция

t :: (Applicative f, Applicative g) => f a -> g a

сохраняющая операции Applicative, то есть

t (pure x) = pure x
t (f <*> x) = t f <*> t x

а функтор тождества Identity и функторы композиции Compose взяты из Data.Functor.Identity и Data.Functor.Compose.

Следствием закона естественности является закон чистоты для traverse

traverse pure = pure

Экземпляры суперклассов должны удовлетворять следующему:

  • В экземпляре Functor метод fmap должен быть эквивалентен обходу с аппликативным функтором тождества (fmapDefault).
  • В экземпляре Foldable метод foldMap должен быть эквивалентен обходу с константным аппликативным функтором (foldMapDefault).

Примечание: суперкласс Functor означает, что (в GHC) обходимые (Traversable) структуры не могут накладывать никаких ограничений на тип элемента. Реализация Haskell с поддержкой функторов с ограничениями могла бы сделать возможным определение структур Traversable с ограничениями.

См. также

  • "The Essence of the Iterator Pattern", by Jeremy Gibbons and Bruno Oliveira, in Mathematically-Structured Functional Programming, 2006, онлайн по адресу http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/#iterator.
  • "Applicative Programming with Effects", by Conor McBride and Ross Paterson, Journal of Functional Programming 18:1 (2008) 1-13, онлайн по адресу http://www.soi.city.ac.uk/~ross/papers/Applicative.html.
  • "An Investigation of the Laws of Traversals", by Mauro Jaskelioff and Ondrej Rypacek, in Mathematically-Structured Functional Programming, 2012, онлайн по адресу http://arxiv.org/pdf/1202.2919.

© 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/Data-Traversable.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API