一个更通用的Functor类

3

这个问题可能看起来有点傻,但我会先问出来,然后解释一下背后的原理。

假设我们按照以下方式重新定义Functor

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}

class Functor a where
  generic_fmap :: a

instance Functor ((a -> b) -> Maybe a -> Maybe b) where
  generic_fmap f (Just x) = Just (f x)
  generic_fmap _ Nothing = Nothing

instance Functor ((a -> b) -> [a] -> [b]) where
  generic_fmap = map

-- etc with more instances    

然后,我考虑了两种定义 fmap 的方法。
(1):
fmap :: (Functor ((a -> b) -> f a -> f b)) => (a -> b) -> f a -> f b
fmap = generic_fmap

或者(2)需要不可判定实例的情况下(已编辑):
class Functor f a b where
  fmap :: (a -> b) -> f a -> f b

instance (GenericFunctor ((a -> b) -> f a -> f b)) => Functor f a b where
  fmap = generic_fmap

除了打字速度变慢和不美观之外,我们在这两种方式中定义fmap时还会失去什么吗?
我问这个问题的原因是,我发现标准的Functor类比数学定义更加具体,因此许多本应符合Functor的事物在Haskell中却不能作为Functor。我的想法是定义一个非常通用的Functor类(也许并不像上面那么通用)。但是,如果它太通用,您会失去类型推断。因此,用户可以根据需要选择使用不同版本的fmap,就像用户可以在prelude中选择.(仅适用于标准函数)或者在Control.Category中选择.(适用于所有范畴)一样。
我知道还有许多向后兼容性问题,但我的第一个问题是,我的定义是否与实例中的用户(暂不考虑那些想要定义实例的人)相同。

你编辑了你的问题,向Functor添加了ab参数。这使得它成为一个非常不同的问题:现在可以使用GHC进行定义(我的原始答案不再适用于此版本)。然而,与此同时,你的问题的答案现在是明确的否定:现在有许多用例无法正常工作,即任何你不想提前固定ab的情况。即使它仍然有效,所有类型签名也需要更改。 - Ørjan Johansen
各种可用的Functor类的泛化([1] [2] [3]),都不够通用吗? - leftaroundabout
2个回答

4
据我所知,您的定义在当前的GHC中不起作用,即使使用了UndecidableInstances

问题在于,您需要一种假设性的扩展"RankNConstraints",它将允许您在最后的实例声明中提到ab,尽管它们不在头部,例如:
instance (forall a b. GenericFunctor ((a -> b) -> f a -> f b)) => Functor f where

我认为这个扩展功能很有用,可以用于许多目的(例如,Stackoverflow答案中出现了它似乎很相关)。自从SPJ和Ralph Hinze在2000年提出一篇论文以来,许多人也这样认为。
不幸的是,最近我从一个旧的GHC票据中得知,由于它会对类型推导造成严重影响,所以不知道如何合理地实现它。

1
这个问题难道不是(至少部分地)由Data.Constraint.Forall解决的吗? - dfeuer
3
很抱歉,Hackage上的版本存在问题(详见https://github.com/ekmett/constraints/issues/11)。在GitHub上有一个新版本是安全的(至少目前我们认为是这样),但无法像之前那样自动推断。 - Ørjan Johansen
哦,伤心。实现unsafeCoerce的工作做得不错。但我希望我能理解它。你发现了什么弱点? - dfeuer
2
@dfeuer “Forall”技巧依赖于没有办法区分约束“对于两个隐藏的具体类型AB是否成立”和“对于所有类型是否成立”。不幸的是,类型相等约束打破了这个假设,并且类型族使得将其升级到“unsafeCoerce”成为可能。(封闭族比较容易-第一个更复杂的版本可以改编为开放式族。可能GADTs也可以工作。) - Ørjan Johansen
2
@dfeuer 噢,对于最简单的版本,大部分剩余的复杂性都在于将所有内容分成足够小、足够隐蔽的步骤,以至于 GHC 没有看到足够多的东西来意识到某些事情是非常错误的。 - Ørjan Johansen

2
我认为更大的问题在于,一个Functor不仅仅是一个map,还有一些相关的法则。我不知道你如何表达所提出的GenericFunctor的规则。
例如,在Category中的id和(.)定义了恒等和复合的意义-现在你可以为一对Categories定义一个GenericFunctor。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接