(Следуя предложению @dfeuer в комментариях.)
Существует конструкция, называемая свертка дня , которая позволяет сохранить различие между двумя функторами при выполнении аппликативных операций и задержать момент преобразования одного в другой.
Тип Day
- это просто пара функторных значений вместе с функцией, которая объединяет их соответствующие результаты:
data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)
Обратите внимание, что фактические возвращаемые значения функторов уже существуют; возвращаемое значение композиции равно значению функции.
Day
имеет преимущества перед другими способами комбинирования аппликативных функторов. В отличие от Sum
, композиция по-прежнему аппликативна. В отличие от Compose
, композиция является «беспристрастной» и не устанавливает порядок вложения. В отличие от Product
, он позволяет нам легко комбинировать аппликативные действия с различными типами возврата, нам просто нужно предоставить подходящую функцию адаптера.
Например, вот два Day ZipList (Vec Nat2) Char
значения:
{-# LANGUAGE DataKinds #-}
import Data.Functor.Day -- from "kan-extensions"
import Data.Type.Nat -- from "fin"
import Data.Vec.Lazy -- from "vec"
import Control.Applicative
day1 :: Day ZipList (Vec Nat2) Char
day1 = Day (pure ()) ('b' ::: 'a' ::: VNil) (flip const)
day2 :: Day ZipList (Vec Nat2) Char
day2 = Day (ZipList "foo") (pure ()) const
(Nat2
из пакета fin , он используется для параметризации фиксированного размера Vec
из vec .)
Мы можем просто сжать их вместе:
res :: Day ZipList (Vec Nat2) (Char,Char)
res = (,) <$> day1 <*> day2
А затем преобразовать Vec
в ZipList
и свернуть Day
:
res' :: ZipList (Char,Char)
res' = dap $ trans2 (ZipList . toList) res
ghci> res'
ZipList {getZipList = [('b','f'),('a','o')]}
Использование функций dap
и trans2
.
Возможный улов производительности: когда мы поднимаем один из функторов до Day
, другому присваивается фиктивное значение pure ()
. Но это мертвый вес при сочетании Day
с (<*>)
. Можно работать умнее, оборачивая функторы в Lift
для трансформаторов, чтобы получить более быстрые операции для простых «чистых» случаев.