[英]Functor instance for generic polymorphic ADTs in Haskell?
When it comes to applying category theory for generic programming Haskell does a very good job, for instance with libraries like recursion-schemes
. 当将类别理论应用于泛型编程时,Haskell做得非常好,例如像recursion-schemes
这样的库。 However one thing I'm not sure of is how to create a generic functor instance for polymorphic types. 然而,我不确定的一件事是如何为多态类型创建通用仿函数实例。
If you have a polymorphic type, like a List or a Tree, you can create a functor from (Hask × Hask) to Hask that represents them. 如果你有一个多态类型,比如List或Tree,你可以创建一个从(Hask×Hask)到Hask的仿函数代表它们。 For example: 例如:
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
These types are polymorphic on A but are fixed points regarding B, something like this: 这些类型在A上是多态的,但是关于B是固定点,如下所示:
newtype Fix f = Fix { unFix :: f (Fix f) }
type List a = Fix (ListF a)
type Tree a = Fix (TreeF a)
But as most know, lists and trees are also functors in the usual sense, where they represent a "container" of a
's, which you can map a function f :: a -> b
to get a container of b
's. 但正如大多数人知道,列表和树木也都在通常意义上,他们代表的“容器”仿函数a
的,这样你可以映射函数f :: a -> b
获得的容器b
的。
I'm trying to figure out if there's a way to make these types (the fixed points) an instance of Functor
in a generic way, but I'm not sure how. 我试图弄清楚是否有办法以通用的方式将这些类型(固定点)作为Functor
一个实例,但我不确定如何。 I've encountered the following 2 problems so far: 到目前为止我遇到了以下两个问题:
1) First, there has to be a way to define a generic gmap
over any polymorphic fixed point. 1)首先,必须有一种方法来定义任何多态固定点上的通用gmap
。 Knowing that types such as ListF
and TreeF
are Bifunctors, so far I've got this: 知道像ListF
和TreeF
这样的类型是Bifunctors,到目前为止我已经得到了这个:
{-# 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
In Haskell this gives me the following error: Could not deduce (Functor (fa)) arising from a use of cata from the context (Bifunctor f)
. 在Haskell中,这给了我以下错误: Could not deduce (Functor (fa)) arising from a use of cata from the context (Bifunctor f)
。
I'm using the bifunctors
package, which has a WrappedBifunctor
type that specifically defines the following instance which could solve the above problem: Bifunctor p => Functor (WrappedBifunctor pa)
. 我正在使用bifunctors
包,它有一个WrappedBifunctor
类型,专门定义了以下可以解决上述问题的实例: Bifunctor p => Functor (WrappedBifunctor pa)
。 However, I'm not sure how to "lift" this type inside Fix
to be able to use it 但是,我不确定如何在Fix
“提升”这种类型以便能够使用它
2) Even if the generic gmap
above can be defined, I don't know if it's possible to create a generic instance of Functor
that has fmap = gmap
, and can instantly work for both the List
and Tree
types up there (as well as any other type defined in a similar fashion). 2)即使可以定义上面的通用gmap
,我也不知道是否可以创建一个具有fmap = gmap
的Functor
的通用实例,并且可以立即为那里的List
和Tree
类型工作(以及以类似方式定义的任何其他类型)。 Is this possible? 这可能吗?
If so, would it be possible to make this compatible with recursion-schemes
too? 如果是这样,是否可以使其与recursion-schemes
兼容?
If you're willing to accept for the moment you're dealing with bifunctors, you can say 你可以说,如果你愿意接受你正在与bifunctors打交道的那一刻
cata :: Bifunctor f => (f a r -> r) -> Fix (f a) -> r
cata f = f . bimap id (cata f) . unFix
and then 然后
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
, I've just rearranged your class constraint to make scoped type variables work.) (在gmap
,我刚刚重新安排了类约束,以使作用域类型变量起作用。)
You can also work with your original version of cata
, but then you need both the Functor
and the Bifunctor
constraint on gmap
: 您也可以使用原始版本的cata
,但是您需要在gmap
上使用Functor
和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
You cannot make your gmap
an instance of the normal Functor
class, because it would need to be something like 你不能让你的gmap
成为普通Functor
类的一个实例,因为它需要是类似的东西
instance ... => Functor (\ x -> Fix (f x))
and we don't have type-level lambda. 而且我们没有类型级别的lambda。 You can do this if you reverse the two arguments of f
, but then you lose the "other" Functor
instance and need to define cata
specific for Bifunctor
again. 如果你反转f
的两个参数,你可以这样做,但是你失去了“其他” Functor
实例,需要再次为Bifunctor
定义cata
。
[You might also be interested to read http://www.andres-loeh.de/IndexedFunctors/ for a more general approach.] [您可能还有兴趣阅读http://www.andres-loeh.de/IndexedFunctors/以获得更通用的方法。]
TBH I'm not sure how helpful this solution is to you because it still requires an extra newtype
wrapping for these fixed-point functors, but here we go: TBH我不确定这个解决方案对你有多大帮助,因为它仍然需要为这些定点newtype
函数提供额外的新类型包装,但是我们在这里:
cata
if you do some wrapping/unwrapping 如果你做一些包装/展开,你可以继续使用你的通用cata
Given the following two helper functions: 给出以下两个辅助函数:
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
you can define gmap
without any additional constraint on f
: 你可以在f
上定义gmap
而不需要任何额外的约束:
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
你可以Fix . f
Fix . f
into a Functor
via a newtype
Fix . f
成Functor
经由newtype
We can implement a Functor
instance for \\a -> Fix (fa)
by implementing this "type-level lambda" as a newtype
: 我们可以实现一个Functor
例如\\a -> Fix (fa)
通过实施本“型级拉姆达”作为newtype
:
newtype FixF f a = FixF{ unFixF :: Fix (f a) }
instance (Bifunctor f) => Functor (FixF f) where
fmap f = FixF . gmap f . unFixF
The bifunctors
package also offers a version of Fix
that's especially appropriate: bifunctors
包还提供了一个特别合适的Fix
版本:
newtype Fix p a = In {out :: p (Fix p a) a}
This is made a Functor
instance rather easily: 这很容易成为一个Functor
实例:
instance Bifunctor p => Functor (Fix p) where
fmap f = In . bimap (fmap f) f . out
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.