Higher-kinded 类型类的量化约束

Quantified Constraints for Higher-kinded Typeclasses

假设我想写两个类型类。 Header:

{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances #-}

import Data.Complex

第一个类型类 ExClass 是这样定义的:

class (forall a. (Monoid (t a))) => ExClass t where
    exFunc :: [t (Complex a)] -> t (Complex a)
    exFunc = mconcat -- the actual function is more complicated
    exFunc2 :: (RealFloat a) => t (Complex a) -> a

我将其定义为 higher-kinded 类型类,因为其函数的输出类型之一取决于其嵌套值的类型 a。我还想为 exFunc 提供一个默认实现,但它涉及 t a 是一个 Monoid 的假设。现在我想为以下类型编写一个实例:

newtype ExType a = ExType a

ExType a 仅当 Num a 为真时才是 Monoid:

instance (Num a) => Semigroup (ExType a) where
    ExType a <> ExType b = ExType (a * b)
instance (Num a) => Monoid (ExType a) where
    mempty = ExType 1

现在我继续为ExClass定义类型类实例,指定Num a的约束:

instance (forall a. Num a) => ExClass ExType where
    exFunc2 (ExType a) = magnitude a

以上代码编译没有问题。但是,如果我尝试像这样使用已实现的功能:

x = ExType 2 :: ExType (Complex Double)

func = exFunc2 x

我收到以下投诉:

• No instance for (Num a) arising from a use of ‘exFunc2’
  Possible fix: add (Num a) to the context of a quantified context
• In the expression: exFunc2 x
  In an equation for ‘func’: func = exFunc2 x

当我使用不同的实例声明时也会发生这种情况:

instance (forall a. Monoid(ExType a)) => ExClass ExType where
    exFunc2 (ExType a) = magnitude a

有没有办法让这个类型类工作?还是我根本不应该像这样构建我的程序?

我认为这里同时出现了几个推理错误。

第一:当你写作时

class (forall a. Monoid (t a)) => ExClass t

这意味着如果有人想实现一个 ExClass t 实例,那么他们必须证明有一个 instance Monoid (t a) where ... 形式的实例 没有任何限制a 上。另请注意,a 的所有可能选择都有实例是不够的——实例本身必须是参数化的。

其次:写的时候

instance (forall a. Num a) => ExClass ExType

这里说的aExClass定义里说的a没有什么神奇的联系。

第三:写的时候

instance (forall a. Num a) => ExClass ExType

这实际上还没有创建 ExClass ExType 的实例。它使实例 条件 成为 forall a. Num a 的证明。总的来说,instance Ctx => C t的意思是任何人想假设C t这个事实,就必须能够自己证明Ctx。没有人能够合理地展示 forall a. Num a,所以这个实例不可用。

这最后两条评论基本上没有改变地适用于您的第二次尝试,

instance (forall a. Monoid(ExType a)) => ExClass ExType

不幸的是,如果没有关于您尝试做什么以及原因的更多详细信息,则几乎不可能提出正确的前进路线。可能性包括类似索引 monad 的方法,将 class 约束从 class 上下文移动到各种方法上下文,根本不创建 class,等等。

Daniel Wagner 已经在他的回答中解释了当前定义的问题。

您似乎希望 ExClass 表示类似“class 的容器类型,它们与应用于其元素的另一个 class c 有特殊关系,当它们的元素满足c时,容器本身就是幺半群。

例如:ExTypeNum有着特殊的关系。当ExType的元素a满足Num时,ExType a变成Monoid.

(这在ExTypeMonoid实例中已经得到肯定,但似乎你想用更高层次的抽象来表达它;为那些容器提供class以类似的方式变成幺半群。)

在 Haskell 中,有多种可能的方式来表达两种类型之间或一种类型与一种 Constraint 之间的关系。让我们使用 MultiParameterTypeClasses:

{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ConstraintKinds #-}

import Data.Kind (Type, Constraint)
-- This kind signature is not strictly required, but makes things clearer
type ExClass :: (Type -> Constraint) -> (Type -> Type) -> Constraint
class (forall a . c a => Monoid (t a)) => ExClass c t | t -> c where
    exFunc :: c a => [t a] -> t a
    exFunc = mconcat 

注意 ExClass 现在有两个参数,并且容器的类型 f 决定(通过函数依赖)约束 c 的元素需要f 因为 fMonoidc 可能因 f 不同而不同!

ExTypeSemigroupMonoid 实例没有改变。 ExTypeExClass 实例现在是:

instance ExClass Num ExType where
    exFunc = mconcat

投入使用:

main :: IO ()
main = print $ exFunc [ExType (1::Int), ExType 2]

(我省略了 Complex 数据类型,这可能会在定义中引发另一个问题。)

I would also like to have a default implementation for exFunc, but it involves the assumption that t a is a Monoid

您只需要 Monoid (t a) 即可获得默认实现?那是倒退。如果该约束不是概念上作为ExClass的超级约束所必需的,那么它不应该在class头部。

您仍然可以使用更严格的默认实现:

{-# LANGUAGE DefaultSignatures #-}

class ExClass t where
  exFunc :: [t (Complex a)] -> t (Complex a)
  default exFunc :: Monoid (t (Complex a)) => [t (Complex a)] -> t (Complex a)
  exFunc = mconcat
  exFunc = mconcat -- the actual function is more complicated
  exFunc2 :: (RealFloat a) => t (Complex a) -> a

然后就是

instance ExClass ExType where
  exFunc2 (ExType a) = magnitude a

您仍然可以提供根本不满足 Monoid 约束的实例,只是那时您还需要手动实施 exFunc

更简单的是根本不用担心默认实现,而只是提供一个帮助器,可以用来获得一个简单的实现而无需复制任何代码:

class ExClass t where
  exFunc :: [t (Complex a)] -> t (Complex a)
  exFunc = mconcat -- the actual function is more complicated
  exFunc2 :: (RealFloat a) => t (Complex a) -> a

monoidIshExFunc :: Monoid (t (Complex a)) => [t (Complex a)] -> t (Complex a)
monoidIshExFunc = mconcat

instance ExClass ExType where
  exFunc = monoidIshExFunc
  exFunc2 (ExType a) = magnitude a

monoidIshExFunc 甚至可以要求 ExClass t,显然你应该小心不要以循环定义结束!)