Экземпляр функтора для общих полиморфных ADTs в Haskell?
когда дело доходит до применения теории категорий для общего программирования Haskell делает очень хорошую работу, например, с библиотеками, такими как recursion-schemes
. Однако я не уверен в том, как создать общий экземпляр функтора для полиморфных типов.
если у вас есть полиморфный тип, например список или дерево, вы можете создать функтор от (Hask × Hask) до Hask, который их представляет. Например:
data ListF a b = NilF | ConsF a b -- L(A,B) = 1+A×B
data TreeF a b = EmptyF | NodeF a b b -- T(A,B) = 1+A×B×B
эти типы полиморфны на A, но являются фиксированными точками что касается B, что-то вроде этого:
newtype Fix f = Fix { unFix :: f (Fix f) }
type List a = Fix (ListF a)
type Tree a = Fix (TreeF a)
но, как известно, списки и деревья также являются функторами в обычном смысле, где они представляют собой "контейнер"a
' s, который вы можете отобразить функцию f :: a -> b
чтобы получить контейнер b
s.
я пытаюсь выяснить, есть ли способ сделать эти типы (фиксированные точки) экземпляром Functor
в общем смысле, но я не уверен, как. Я столкнулся следующие 2 проблемы так далеко:
1) во-первых, должен быть способ определить общий gmap
над любой полиморфной фиксированной точкой. Зная, что такие типы, как ListF
и TreeF
являются Бифункторами, до сих пор я получил это:
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Bifunctor
newtype Fix f = Fix { unFix :: f (Fix f) }
cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = f . fmap (cata f) . unFix
-- To explicitly use inF as the initial algebra
inF :: f (Fix f) -> Fix f
inF = Fix
gmap :: forall a b f. Bifunctor f => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
в Haskell это дает мне следующую ошибку: Could not deduce (Functor (f a)) arising from a use of cata from the context (Bifunctor f)
.
я использую bifunctors
пакета, который имеет WrappedBifunctor
тип, который конкретно определяет следующий экземпляр, который может решить вышеуказанную проблему:Bifunctor p => Functor (WrappedBifunctor p a)
. Однако, я не уверен, как "поднять" этот тип внутри Fix
уметь использовать
2) даже если общий gmap
выше можно определить, я не знаю, можно ли создать общий экземпляр Functor
и fmap = gmap
, и может мгновенно работать как для List
и Tree
типы там (а также любой другой тип, определенный аналогичным образом). Возможно ли это?
если да, можно ли сделать это совместимым с recursion-schemes
тоже?
3 ответов
TBH я не уверен, насколько полезно это решение для вас, потому что оно по-прежнему требует дополнительного newtype
упаковка для этих функторов с фиксированной точкой, но здесь мы идем:
вы можете продолжать использовать свой generic cata
если вы делаете некоторые упаковки / разворачивания
даны следующие две вспомогательные функции:
unwrapFixBifunctor :: (Bifunctor f) => Fix (WrappedBifunctor f a) -> Fix (f a)
unwrapFixBifunctor = Fix . unwrapBifunctor . fmap unwrapFixBifunctor . unFix
wrapFixBifunctor :: (Bifunctor f) => Fix (f a) -> Fix (WrappedBifunctor f a)
wrapFixBifunctor = Fix . fmap wrapFixBifunctor . WrapBifunctor . unFix
вы можете определить gmap
без каких-либо дополнительных ограничений на f
:
gmap :: (Bifunctor f) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = unwrapFixBifunctor . cata alg . wrapFixBifunctor
where
alg = inF . bimap f id
можно сделать Fix . f
на Functor
через a newtype
мы можем реализовать Functor
экземпляр \a -> Fix (f a)
реализуя этот "тип-уровень лямда -" как newtype
:
newtype FixF f a = FixF{ unFixF :: Fix (f a) }
instance (Bifunctor f) => Functor (FixF f) where
fmap f = FixF . gmap f . unFixF
если вы готовы принять на данный момент Вы имеете дело с бифункторами, вы можете сказать
cata :: Bifunctor f => (f a r -> r) -> Fix (f a) -> r
cata f = f . bimap id (cata f) . unFix
а то
gmap :: forall a b f. Bifunctor f => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
(In gmap
, Я только что изменил ваше ограничение класса, чтобы переменные типа области работали.)
вы также можете работать с оригинальной версией cata
, но тогда нужно как
Functor
и Bifunctor
ограничение gmap
:
gmap :: forall a b f. (Bifunctor f, Functor (f a)) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
вы не можете сделать свой gmap
экземпляр нормальный Functor
класс, потому что это должно быть что-то вроде
instance ... => Functor (\ x -> Fix (f x))
и у нас нет лямбда уровня типа. Вы можете сделайте это, если вы отмените два аргумента f
, но тогда вы потеряете "друга" Functor
экземпляр и нужно определить cata
характерные для Bifunctor
снова.
[вам также может быть интересно прочитать http://www.andres-loeh.de/IndexedFunctors/ для более общего подхода.]
на bifunctors
пакет также предлагает версию Fix
Это особенно уместно:
newtype Fix p a = In {out :: p (Fix p a) a}
этот Functor
экземпляр довольно легко:
instance Bifunctor p => Functor (Fix p) where
fmap f = In . bimap (fmap f) f . out