Haskell中泛型多态ADT的函子实例?
当谈到将范畴理论应用于泛型编程时,Haskell做得非常好,例如使用了Haskell中泛型多态ADT的函子实例?,haskell,recursion,functor,category-theory,Haskell,Recursion,Functor,Category Theory,当谈到将范畴理论应用于泛型编程时,Haskell做得非常好,例如使用了递归方案之类的库。但是有一件事我不确定,那就是如何为多态类型创建泛型函子实例 如果您有多态类型,如列表或树,则可以创建一个从(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上是多态的,
递归方案
之类的库。但是有一件事我不确定,那就是如何为多态类型创建泛型函子实例
如果您有多态类型,如列表或树,则可以创建一个从(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
的“容器”,您可以映射函数f::a->b
,以获得b
的容器
我试图找出是否有一种方法可以使这些类型(固定点)以通用方式成为Functor
的实例,但我不确定如何实现。到目前为止,我遇到了以下两个问题:
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中,这给了我以下错误:无法从上下文(Bifunctor f)推断使用cata时产生的(Functor(fa))
我使用的是bifunctors
包,它有一个WrappedBifunctor
类型,专门定义了以下实例,可以解决上述问题:Bifunctor p=>Functor(WrappedBifunctor p a)
。但是,我不知道如何在Fix
中“提升”这种类型才能使用它
2)即使可以定义上面的通用gmap
,我也不知道是否有可能创建一个Functor
的通用实例,该实例具有fmap=gmap
,并且可以立即用于上面的列表和树
类型(以及以类似方式定义的任何其他类型). 这可能吗
如果是这样的话,有没有可能使它也与递归方案兼容呢?如果你愿意在处理biFunctor时接受,你可以说
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
(在gmap
中,我刚刚重新安排了类约束,以使作用域类型变量起作用。)
您也可以使用原始版本的cata
,但是您需要同时使用
Functor
和gmap
上的Bifunctor
约束:
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))
我们没有类型级别的lambda。如果您反转了f
的两个参数,那么您可以执行此操作,但随后您将丢失“other”Functor
实例,并且需要再次为Bifunctor
定义cata
特定
[您可能也有兴趣阅读更一般的方法。]TBH我不确定此解决方案对您有多大帮助,因为这些定点函子仍然需要额外的newtype
包装,但现在我们开始:
如果进行一些包装/展开,您可以继续使用您的通用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
通过newtype
通过将此“类型级别lambda”实现为新类型,我们可以为\a->Fix(fa)
实现一个Functor
实例:
newtype FixF f a = FixF{ unFixF :: Fix (f a) }
instance (Bifunctor f) => Functor (FixF f) where
fmap f = FixF . gmap f . unFixF
bifunctors
软件包还提供了一个特别合适的Fix
版本:
newtype Fix p a = In {out :: p (Fix p a) a}
这很容易成为一个函子
实例:
instance Bifunctor p => Functor (Fix p) where
fmap f = In . bimap (fmap f) f . out
这是您的法定提醒,因为“ADT”同时代表“代数数据类型”和“抽象数据类型”,而且这两个概念强烈对立,“ADT”一词毫无意义,最好避免使用。@pigworker:从现在起我们称之为UGADT怎么样?有可能以不同的方式处理这个问题吗,那么编写函子的实例就成为可能了?使用不同的类型,可能是类型类等?bimap f id
可能应该写入Data.Bifunctor.first
。