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
这里说的a
和ExClass
定义里说的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
时,容器本身就是幺半群。
例如:ExType
与Num
有着特殊的关系。当ExType
的元素a
满足Num
时,ExType a
变成Monoid
.
(这在ExType
的Monoid
实例中已经得到肯定,但似乎你想用更高层次的抽象来表达它;为那些容器提供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
因为 f
是 Monoid
。 c
可能因 f
不同而不同!
ExType
的 Semigroup
和 Monoid
实例没有改变。 ExType
的 ExClass
实例现在是:
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
,显然你应该小心不要以循环定义结束!)
假设我想写两个类型类。 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
这里说的a
和ExClass
定义里说的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
时,容器本身就是幺半群。
例如:ExType
与Num
有着特殊的关系。当ExType
的元素a
满足Num
时,ExType a
变成Monoid
.
(这在ExType
的Monoid
实例中已经得到肯定,但似乎你想用更高层次的抽象来表达它;为那些容器提供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
因为 f
是 Monoid
。 c
可能因 f
不同而不同!
ExType
的 Semigroup
和 Monoid
实例没有改变。 ExType
的 ExClass
实例现在是:
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 thatt a
is aMonoid
您只需要 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
,显然你应该小心不要以循环定义结束!)