简体   繁体   English

Haskell中泛型多态ADT的Functor实例?

[英]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: 知道像ListFTreeF这样的类型是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 = gmapFunctor的通用实例,并且可以立即为那里的ListTree类型工作(以及以类似方式定义的任何其他类型)。 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上使用FunctorBifunctor约束:

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函数提供额外的新类型包装,但是我们在这里:

You can keep using your generic 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

You can make Fix . f 你可以Fix . f Fix . f into a Functor via a newtype Fix . fFunctor经由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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM