使用递归方案的泛型多态代数数据类型的Haskell函子实例 问题:
最近我在这里问了以下问题,询问如何为任意多态ADT(代数数据类型),如列表、树等创建通用映射函数和使用递归方案的泛型多态代数数据类型的Haskell函子实例 问题:,haskell,recursion,functor,algebraic-data-types,category-theory,Haskell,Recursion,Functor,Algebraic Data Types,Category Theory,最近我在这里问了以下问题,询问如何为任意多态ADT(代数数据类型),如列表、树等创建通用映射函数和Functor的通用实例: 现在,我正试图重新格式化上述内容,使之与递归方案兼容。也就是说,我想一方面定义类型,另一方面定义基函子,并使用base族类型将它们关联起来,而不是定义基函子,然后将类型定义为其固定点 因此,与其这样做,不如: data ListF a b = NilF | ConsF a b newtype Fix f = Fix { unFix :: f (Fix f) } type
Functor
的通用实例:
现在,我正试图重新格式化上述内容,使之与递归方案兼容。也就是说,我想一方面定义类型,另一方面定义基函子,并使用base
族类型将它们关联起来,而不是定义基函子,然后将类型定义为其固定点
因此,与其这样做,不如:
data ListF a b = NilF | ConsF a b
newtype Fix f = Fix { unFix :: f (Fix f) }
type List a = Fix (ListF a)
我想这样做:
data ListF a b = NilF | ConsF a b
data List a = Nil | Cons a (List a)
type instance Base (List a) = ListF a
通过这种方式,我可以利用递归方案
库的强大功能,同时仍然能够为这些多态类型中的任何一种定义通用的fmap
。不仅如此,能够使用“正常”类型,而不是作为固定点的类型同义词,这是一种更愉快的体验
尝试:
最初,我考虑一方面使用Bifunctor
实例,然后以某种方式强制或使其与相应的Base
家族实例相等。目前我只能考虑使用Data.Type.Equality
中的a:~:b
。到目前为止,我得到的是:
{-# LANGUAGE TypeOperators, Rank2Types #-}
import Data.Bifunctor
import Data.Functor.Foldable
import Data.Type.Equality
gmap :: (Bifunctor p, Foldable (f a), Unfoldable (f b)) =>
(forall x. p x :~: Base (f x)) -> (a -> b) -> f a -> f b
gmap refl f = cata alg
where
alg = embed .
castWith (apply refl Refl) .
bimap f id .
castWith (apply (sym refl) Refl)
但是,我的问题在于试图定义Functor
的实例。我不知道在定义实例时如何指定这些特定的类型约束。
我在考虑以某种方式创建一个typeclassEquals
,并执行如下操作:
instance (Bifunctor p, Foldable (f a), Unfoldable (f b), Equals (p a) (Base (f a)))
=> Functor f where
但我不知道这是否可能,也不知道我是否以正确的方式处理它(例如,我不确定我对gmap
的定义是否正确)
作为参考,这是原始SO问题中通用gmap
的定义:
更新:
有人指出,gmap
的以下定义将更为一般,不需要任何类型级别相等的奇怪应用:
gmap :: (Foldable t, Unfoldable d, Bifunctor p, Base d ~ p b, Base t ~ p a)
=> (a -> b) -> t -> d
gmap f = cata ( embed . bimap f id )
然而,我仍然找不到一种方法来创建具有类似类型约束的Functor
的实例,只要您对不可判定的实例没有意见,我就能够拼凑出一个有效的版本
这个想法是通过要求所有x的,从gmap
的上下文中删除对a
和b
的所有引用。可折叠(f x)
等,使用软件包编码:
有了a
和b
,我们可以把gmap
变成fmap
:
{-# LANGUAGE UndecidableInstances #-}
instance (Bifunctor p, ForallF Foldable f, ForallF Unfoldable f, Forall (Based p f)) => Functor f where
fmap = gmap
编辑以添加:上述实例的问题在于它将匹配正确类型的任何类型,如@gonzaw:所述,如果您有
data ListT a = NilT
| ConsT a (ListT a)
data ListF a b = NilF
| ConsF a b
type instance Base (ListT a) = ListF a
instance Bifunctor ListF where ...
instance Functor (ListF a) where ...
instance Foldable (ListT a) where ...
instance Unfoldable (ListT a) where ...
然后你得到了比你预想的更多的东西,并且泛型的函子
实例和listfa(!)的实例重叠
您可以再添加一层新类型包装来解决这个问题:如果您有
newtype F f x = F{ unF :: (f x) }
instance (Bifunctor p, ForallF Foldable f, ForallF Unfoldable f, Forall (Based p f)) => Functor (F f) where
fmap f = F . gmap f . unF
type ListT' = F ListT
最后进行以下类型检查:
*Main> unF . fmap (+1) . F $ ConsT 1 $ ConsT 2 NilT
ConsT 2 (ConsT 3 NilT)
您是否可以接受这层额外的newtype
包装,这是您必须决定的事情。1。:~:
的意义是什么?如果必须始终使用Refl
调用函数,则该函数可能不实用。2.如果我写gmap f=cata(embed.bimap f id)
并让编译器推断类型,我得到(可折叠的t,可展开的d,双函数p,基d~pb,基t~pa)=>(a->b)->t->d
。这种最普通的类型有什么问题吗?如果你喜欢,你可以有一个更具体的类型。编译器对你的函数非常满意,所以我不知道实际的问题是什么。@user2405038我想用fmap=gmap
创建一个Functor
的实例!不过这只是个小怪癖。我试图用一个典型的列表类型来测试这一点,比如说datalistta=consta(listta)|NilT
,datalistfab=NilF | consfab
,类型实例库(listta)=listfa
。我创建了一个Bifunctor
的实例,当我尝试创建一个实例可折叠(ListT a)时,其中出现以下错误:无法将类型“Base(ListF a Data.Constraint.Forall.a)”与“p1 Data.Constraint.Forall.a”匹配,类型变量p1不明确
。我使用了可展开的
,这是因为可怕的函子
实例不仅与列表
匹配,还与列表
匹配……太好了,现在它可以工作了!我不介意新型包装。从我所读到的内容来看,最好的做法是使用一个新的类型包装器,使这些常见类型类(Applicative、Functor、Monad)的“外来”自动类型的实例,以防止孤立实例等。在使用ListT
时,我总是可以单独使用gmap
,但是如果我需要使用Functor
的功能,我只需要快速包装和打开它。如果它让我那么烦恼的话,我只想包括一个快速的实例函子列表,其中fmap f=unF。FMAPF。事实上,我想知道是否可以使用Template Haskell来包含那个小的Functor ListT
实例。无论类型如何,该定义都是相同的。如果我有另一种类型,比如TreeT
,它仍然是fmap f=unF。fmap f。F
,对吗?如果是这样的话,我想我可以试一下!抱歉三重发布,但这是一个好功能,包括在递归方案库中吗?
newtype F f x = F{ unF :: (f x) }
instance (Bifunctor p, ForallF Foldable f, ForallF Unfoldable f, Forall (Based p f)) => Functor (F f) where
fmap f = F . gmap f . unF
type ListT' = F ListT
*Main> unF . fmap (+1) . F $ ConsT 1 $ ConsT 2 NilT
ConsT 2 (ConsT 3 NilT)