使用 ScopedTypeVariables 来约束 fmap 函数参数

Using ScopedTypeVariables to constrain fmap function argument

我有以下用例:我正在构建自定义 AST。作为对我在 AST 上进行的某些操作的优化,我为 AST 节点定义了一个子节点列表:

data NodeChHolder a = NNode [a] -- For "normal" operators
                    | ACNode (MultiSet a) -- For operators that can be re-ordered and parenthesized arbitrarily
                    | NCNode -- Empty, no children

现在,我想让这个类型成为一个 Functor。但是,有一个问题,因为 MultiSet 要求其类型参数为 Ord。所以,这没有用:

instance Functor NodeChHolder where
  fmap f (NNode l) = NNode $ map f l
  fmap f (ACNode s) = ACNode $ MultiSet.map f s
  fmap _ NCNode = NCNode

我收到一条错误消息说有 "no instance for Ord b arising from a use of MultiSet.map",这很公平。

为了解决这个问题,我尝试了以下使用 ScopedTypeVariables ghc 扩展的方法。我认为这与它与类型的工作方式类似,但看起来类型类不同:

instance Functor NodeChHolder where
  fmap f (NNode l) = NNode $ map f l
  fmap (f :: (Ord a, Ord b) => a -> b) (ACNode s) = ACNode $ MultiSet.map f s
  fmap f (ACNode s) = NNode $ map f (MultiSet.toList s)
  fmap _ NCNode = NCNode

这也失败并出现相同的错误消息。 接下来,我试着稍微改变一下,因为根据我对 ScopedTypeVariablesforall 的理解,它应该确保我的 ab 类型变量m 使用与 fmap.

相同
instance Functor NodeChHolder where
  fmap f (NNode l) = NNode $ map f l
  fmap (f :: forall a b. (Ord a, Ord b) => a -> b) (ACNode s) = ACNode $ MultiSet.map f s
  fmap f (ACNode s) = NNode $ map f (MultiSet.toList s)
  fmap _ NCNode = NCNode

上面的不行,说的是"couldn't match b with b1",因为他们都是"rigid type variables"。我认为这是因为我需要为 fmap 本身实际声明类型参数 ab,所以我也使用了 InstanceSigs 扩展并最终得到

instance Functor NodeChHolder where
  fmap :: (a -> b) -> NodeChHolder a -> NodeChHolder b
  fmap f (NNode l) = NNode $ map f l
  fmap (f :: forall a b. (Ord a, Ord b) => a -> b) (ACNode s) = ACNode $ MultiSet.map f s
  fmap f (ACNode s) = NNode $ map f (MultiSet.toList s)
  fmap _ NCNode = NCNode

但是关于刚性类型变量,我仍然遇到同样的错误。

在这一点上,我什至不知道我正在尝试做的事情是否可行!我应该放弃尝试使它完全成为一个函子吗?使用 InstanceSigs,我可能会做 fmap :: Ord b => (a -> b) -> NodeChHolder a -> NodeChHolder b,这将适合我的用例,但那将不再是真正的函子...

您不能使用常规 Functor class 执行此操作。这样class有个方法

fmap :: Functor f => (a -> b) -> f a -> f b

不对 ab 施加任何限制。这要求任何实例都可以使用 ab 的任何选择。事实上,如果实例被允许提出额外的要求,那么 fmap 不可能有上面的类型。

然而,您可以使用另一种类型 class 来表示受约束的函子。 包中有一个constrained-monads,它允许下面的代码。

import qualified Control.Monad.Constrained as C

data MultiSet a = Whatever -- stub

multiSet_map :: Ord b => (a -> b) -> MultiSet a -> MultiSet b
multiSet_map = undefined -- stub

data NodeChHolder a = NNode [a]
                    | ACNode (MultiSet a)
                    | NCNode

instance C.Functor NodeChHolder where
  type Suitable NodeChHolder b = Ord b
  fmap f (NNode l) = NNode $ map f l
  fmap f (ACNode s) = ACNode $ multiSet_map f s
  fmap _ NCNode = NCNode