【问题标题】:Haskell - Functor instance for generic polymorphic Algebraic Data Types using recursion-schemesHaskell - 使用递归方案的通用多态代数数据类型的仿函数实例
【发布时间】:2015-03-16 23:43:19
【问题描述】:

问题:

最近我在这里问了以下问题,询问如何为任意多态 ADT(代数数据类型)(如列表、树等)创建通用映射函数和 Functor 的通用实例:

Functor instance for generic polymorphic ADTs in Haskell?

现在,我正在尝试重新制定上述内容以与recursion-schemes 兼容。即,我不想定义基仿函数,然后将类型定义为其固定点,而是一方面定义类型,另一方面定义基仿函数,并使用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

这样我可以利用recursion-schemes 库的强大功能,同时仍然能够为任何这些多态类型定义一个通用的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 的实例。我不知道在定义实例时如何指定那些特定的类型约束。 我正在考虑以某种方式创建一个类型类Equals,然后做这样的事情:

instance (Bifunctor p, Foldable (f a), Unfoldable (f b), Equals (p a) (Base (f a))) 
    => Functor f where

但我不知道这是否可能,也不知道我是否以正确的方式接近它(例如,我不确定我对 gmap 的定义是否正确)。


作为参考,这是来自原始 SO 问题的通用 gmap 的定义:

gmap :: (Bifunctor f) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = unwrapFixBifunctor . cata alg . wrapFixBifunctor
  where
    alg = Fix . bimap f id

    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 的以下定义会更通用,并且不需要任何奇怪的类型级相等应用:

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 实例的方法

【问题讨论】:

  • 1. :~: 有什么意义?如果您必须一直使用Refl 调用它,您的函数可能不切实际。 2. 如果我写gmap f = cata ( embed . bimap f id ) 并让编译器推断类型,我得到(Foldable t, Unfoldable d, Bifunctor p, Base d ~ p b, Base t ~ p a) => (a -> b) -> t -> d。这种最通用的类​​型有什么问题吗?如果你愿意,你可以有更具体的类型。编译器对你的函数非常满意,所以我不知道实际的问题是什么。
  • @user2405038 我想使用fmap = gmap创建Functor的实例

标签: haskell recursion functor algebraic-data-types category-theory


【解决方案1】:

With a little help from @kosmikus,只要你对 UndecidableInstances 没问题,我就能破解一个可以工作的版本。

这个想法是从gmap 的上下文中删除对ab 的所有引用,方法是要求forall x. Foldable (f x) 等等,使用constraints 包进行编码:

{-# LANGUAGE TypeFamilies, ScopedTypeVariables, TypeOperators, ConstraintKinds #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
import Data.Bifunctor
import Data.Functor.Foldable
import Data.Constraint
import Data.Constraint.Forall

-- https://stackoverflow.com/a/28067872/477476
class (p x ~ Base (f x)) => Based p f x
instance (p x ~ Base (f x)) => Based p f x

gmap :: forall p f a b. ( Bifunctor p 
                        , ForallF Foldable f
                        , ForallF Unfoldable f
                        , Forall (Based p f))
     => (a -> b) -> f a -> f b
gmap f = case (instF :: ForallF Foldable f :- Foldable (f a)) of
  Sub Dict -> case (instF :: ForallF Unfoldable f :- Unfoldable (f b)) of
    Sub Dict -> case (inst :: Forall (Based p f) :- Based p f a) of
      Sub Dict -> case (inst :: Forall (Based p f) :- Based p f b) of
        Sub Dict -> cata (embed . bimap f id)

有了ab,我们可以把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 ...

那么你得到的比你预想的要多,通用的 Functor 实例和 ListF a(!) 的实例重叠。

您可以再添加一层 newtype 包装器来解决这个问题:如果您有

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 包装是否适合您是您必须决定的事情。

【讨论】:

  • 太棒了!不过只是一个小怪癖。我正在尝试使用典型的 List 类型来测试它,例如data ListT a = ConsT a (ListT a) | NilTdata ListF a b = NilF | ConsF a btype instance Base (ListT a) = ListF a。我创建了一个Bifunctor 的实例,当我尝试创建一个实例instance Foldable (ListT a) where 时,出现以下错误:Couldn't match type ‘Base (ListF a Data.Constraint.Forall.A)’ with ‘p1 Data.Constraint.Forall.A’ The type variable p1 is ambiguous。同Unfoldable
  • 那是因为那个可怕的Functor 实例不仅匹配ListT,还匹配ListF...
  • 太好了,现在可以了!我不介意新类型的包装。根据我的阅读,最好的做法是使用新类型包装器来制作常见类型类(Applicative、Functor、Monad)的这些“异国情调”自动类型的实例,以防止出现孤儿实例等。在使用ListT 时,我总是可以单独使用gmap,但如果我需要使用Functor 的功能,我会快速包装和打开它。如果它让我很困扰,我会快速添加一个 instance Functor ListT where fmap f = unF . fmap f. F 并完成它
  • 事实上,我想知道是否可以使用 Template Haskell 来包含那个小 Functor ListT 实例。无论类型如何,该定义都是相同的。如果我有另一种类型,比如TreeT,它仍然是fmap f = unF . fmap f . F,对吧?如果是这样,我想我可以尝试一下!
  • 抱歉发了三次,但这是包含在递归方案库中的一个很好的功能吗?
猜你喜欢
  • 2014-08-13
  • 2017-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-01
  • 2022-01-06
  • 2016-09-20
  • 2014-08-15
相关资源
最近更新 更多