Ответ 1
Эта проблема может быть разбита на две части, как вы представляете тип данных и как их составлять вместе.
Типы данных
В приведенных вами стилях используются только 2 типа типов данных, "нормальный" стиль и стиль продолжения прохождения. Они отличаются тем, что объекты выбраны в качестве примитивов языка.
В стандартном стиле типы данных и их конструкторы выбираются как примитивные. Типы данных представляют собой суммы (имеющие несколько конструкторов) продуктов (имеющих несколько значений)
data Sum a b = Left a | Right b
data Product a b = Product a b
Основными объектами языка являются эти типы и функции данных; функции деконструируют данные, чтобы заглянуть внутрь и посмотреть, что они делают.
either :: (a -> c) -> (b -> c) -> Sum a b -> c
either l _ (Left a) = l a
either _ r (Right b) = r b
uncurry :: (a -> b -> c) -> Product a b -> c
uncurry f (Product a b) = f a b
Вы можете создать эквивалентный язык, где универсально квантифицированные типы рассматриваются как примитивные, а не типы данных. В этом случае вы можете определить типы данных с точки зрения универсальной квантификации. Суммы представлены их функцией either
, универсально определенной по типу возврата. Продукты представлены их функцией uncurry
, универсально определенной по типу возврата. Необходимость расширения языка (RankNTypes
) для представления типов данных таким образом подсказывает, почему вы назвали бы первый стиль "нормальным".
{-# LANGUAGE RankNTypes #-}
newtype Product a b = Product (forall r. (a -> b -> r) -> r)
product :: a -> b -> Product a b
product a b = Product (\f -> f a b)
uncurry :: (a -> b -> c) -> Product a b -> c
uncurry both (Product f) = f both
newtype Sum a b = Sum (forall r. (a -> r) -> (b -> r) -> r)
left :: a -> Sum a b
left a = Sum (\l r -> l a)
right :: b -> Sum a b
right b = Sum (\l r -> r b)
either :: (a -> c) -> (b -> c) -> Sum a b -> c
either l r (Sum f) = f l r
Это приводит к одному из основных различий между двумя стилями. В универсально-количественном стиле нет конструкторов. Вся структура данных должна храниться в замыканиях на функции, и именно там помещаются замены для конструкторов left
, right
и product
. В универсально-количественном стиле вы не можете создавать ненужные промежуточные объекты; для вас нет объекта. Вы все равно можете построить ненужные промежуточные блокировки. По крайней мере, вы обманете профайлера, сообщив вам, что у вас нет кучи предметов, висящих вокруг.
Ваш тип данных FooM
, повторяемый здесь, также может быть представлен в стиле продолжения передачи.
data FooM i o a
= Await (i -> FooM i o a)
| Yield o (FooM i o a)
| Done a
Он будет представлен функцией matchFoo
, которую я определил.
matchFoo :: ((i -> FooM i o a) -> r) -> (o -> FooM i o a -> r) -> (a -> r) -> r
matchFoo a _ _ (Await f) = a f
matchFoo _ y _ (Yield o next) = y o next
matchFoo _ _ d (Done a) = d a
Универсально квантифицированный FooM
идентифицирует a FooM
с его функцией matchFoo
, универсальной над его возвращаемым типом.
newtype FooCPS i o a = FooCPS
{ runFooCPS
:: forall r.
((i -> FooCPS i o a) -> r)
-> (o -> FooCPS i o a -> r)
-> (a -> r)
-> r
}
await :: (i -> FooCPS i o a) -> FooCPS i o a
await f = FooCPS (\a _ _ -> a f)
yield :: o -> FooCPS i o a -> FooCPS i o a
yield o next = FooCPS (\_ y _ -> y o next)
done :: a -> FooCPS i o a
done a = FooCPS (\_ _ d -> d a)
Нарушение проблемы в 2
Чтобы использовать тот же тип данных для всех способов их компоновки, мы заменим FooM
его базовым функтором. Базовый функтор - это нормальный тип данных с заменой рекурсий на переменную типа.
data FooF i o a next
= Await (i -> next)
| Yield o next
| Done a
deriving (Functor)
Вы можете эквивалентно определить базовый функтор в стиле продолжения прохождения.
newtype FooFCPS i o a next = FooFCPS
{ runFooCPS
:: forall r.
((i -> next) -> r)
-> (o -> next -> r)
-> (a -> r)
-> r
}
deriving (Functor)
Сопоставление их обратно вместе
- Normal
Мы можем немедленно восстановить FooM
, определив
newtype FooM i o a = FooM (FooF i o a (FooM i o a))
Если вы уже определили неподвижную точку функтора:
newtype Fix f = Fix (f (Fix f))
Тогда FooM
может быть восстановлен на
newtype FooM i o a = FooM (Fix (FooF i o a))
- Стиль перехода к продолжению
Стиль прохождения продолжения может быть немедленно восстановлен из универсально квантифицированного FooFCPS
newtype FooCPS i o a = FooCPS (Fix (FooFCPS i o a))
- Codensity
Трансформатор плотности работает с FooM
или FooCPS
.
- Отражение без сожаления
Мы можем определить отражение без раскаяния в терминах базовых функторов без воспроизведения типа данных FooM
в FooRWR
.
newtype RWR f a = RWR { runRWR :: f (RWRExplicit f a) }
newtype RWRExplicit f a = RWRExplicit (forall x. FTCQueue (RWR f) x a)
И затем восстановите FooRWR
с помощью
newtype FooRWR i o a = FooRWR {runFooRWR :: RWR (FooF i o a) a}
Бонусные наблюдения
Free
Оба Free
и F
будут работать с любым из базовых функторов FooF
или FooFCPS
.
Monad Transformers
Базовый функтор также может использоваться для создания монадного трансформатора. Там подробное обсуждение построения трансформатора MachineT
(которое тесно связано с FooM
) в этом вопросе и ответе.
Утверждение, что par
не может быть записано в CPS без предварительного преобразования в обычный стиль, требует некоторой квалификации, поскольку все типы данных могут быть заменены универсальными квантованными типами продолжения прохождения.