为免费 Monad 定义平等实例

Define Equality Instance for Free Monad

鉴于 Free Monad:

data Free f a = Var a
               | Node (f (Free f a)) 

我试图为它定义一个 Eq 实例:

instance (Functor f, Eq (f a)) => Eq (Free f a) where
    (==) (Var x) (Var y)       = x == y
    (==) (Node fu1) (Node fu2) = fu1 == fu2
    (==) _ _                   = False

但是编译失败:

FreeMonad.hs:17:10:
    Non type-variable argument in the constraint: Eq (f a)
    (Use FlexibleContexts to permit this)
    In the context: (Functor f, Eq (f a))
    While checking an instance declaration
    In the instance declaration for ‘Eq (Free f a)’
Failed, modules loaded: none.

指定 (Functor f, Eq (f a)) 的 constraint/pre-condition 对我来说似乎很奇怪(至少我不认为我以前作为初学者见过它)。

如何为 Free f a 定义 Eq 实例?

Eq (f a)这样的约束没有错。如错误消息所述,您将需要启用(无害的)FlexibleContexts GHC 扩展来执行此操作,因此添加...

{-# LANGUAGE FlexibleContexts #-}

...到源文件的顶部。

但是请注意,(Functor f, Eq (f a)) 并没有真正反映您在实施 (==) 时所做的事情。首先,这里不需要 fFunctor 的假设,因此您可以安全地删除 Functor f 约束。其次,约束应该与您编写不同案例所需的内容相匹配。在第一种情况下,你做 x == yxy 都是 a 类型,所以你需要 Eq a。出于类似的原因,第二种情况需要 Eq (f (Free f a)) 而不是 Eq (f a)。这意味着你最终会得到...

(Eq (f (Free f a)), Eq a) => Eq (Free f a)

... 匹配参考实现,例如 Control.Monad.Free.

中的参考实现

duplode 展示了如何使用灵活的上下文来做到这一点。如果你想要 Haskell 2010,通常的方法是使用 Prelude.Extras 或类似的 Eq1 class。

class Eq1 f where
  (==#) :: Eq a => f a -> f a -> Bool

然后你会使用

instance (Eq1 f, Eq a) => Eq (Free f a) where ...
instance Eq1 f => Eq1 (Free f) -- default instance is fine.

我现在不在电脑旁,所以我要等会儿才能测试这个。