使用MultiParamTypeClasses时,是否需要在每个类函数中使用每种类型

jam*_*idh 5 haskell

当我使用MultiParamTypeClasses时,我可以创建忽略其中一个类型参数的类函数(即下面的"身份").

{-# LANGUAGE MultiParamTypeClasses #-}

data Add = Add
data Mul = Mul

class Test a b where
    identity::a

instance Test Int Add where
    identity = 0

instance Test Int Mul where
    identity = 1
Run Code Online (Sandbox Code Playgroud)

(这是一个精简版本,当然在完整的程序中会有其他功能使用"b").

示例编译,但我永远无法访问身份!

main = do
    putStrLn (show (identity::Int))
Run Code Online (Sandbox Code Playgroud)

导致"使用'身份'时没有(Test Int b0)的实例.

有没有办法获取身份?如果没有,编译器是否应该禁止我创建一个不使用所有类型参数的类函数?

lef*_*out 5

如果没有,编译器是否应该禁止我创建一个不使用所有类型参数的类函数?

也许.事实上,你永远无法使用这样的类方法.但由于错误总是在编译时发生,因此并不是很危险.

在某些类似情况下工作的修复程序(不在您的情况下):

  • 使未确定的类型变量在功能上依赖于其他变量.

    {-# LANGUAGE FunctionalDependencies #-}
    class Group_FD g p | g->p where
      identity :: g
    
    Run Code Online (Sandbox Code Playgroud)

    也许可以这样使用

    data Nat = One | Succ Nat
    
    instance Group_FD Nat Mult where
      identity = One
    
    instance Group_FD Int Add where
      identity = 0
    
    Run Code Online (Sandbox Code Playgroud)

    但显然不可能以g这种方式使用相同的元素创建多个实例.

  • 为仅依赖于该方法的方法定义一个仅包含一个参数的单独类.然后在另一个上使这个类成为约束("超类"),以"导入"方法:

    class Identity i where
      identity :: i
    class (Identity i) => Test i y
    
    Run Code Online (Sandbox Code Playgroud)

    但是,对于你的应用程序来说,这根本没用,因为你想要identity依赖于Phantom类型变量的行为.

为了实现您的目标,您必须以某种方式传递您想要的实例的信息.实现这一目标的一种方法是幻象:

class Group_PA g p where
  identity :: p -> g

instance Group_PA Int Add where
  identity _ = 0

instance Group_PA Int Mult where
  identity _ = 1
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它

GHCi> identity Add :: Int
0
GHCi> identity Mult :: Int
1

也许更惯用的实际上是使标志类型为空

{-# LANGUAGE EmptyDataDecls #-}
data Add
data Mult
Run Code Online (Sandbox Code Playgroud)

GHCi> identity(undefined :: Add):: Int
0
GHCi> identity(undefined :: Mult):: Int
1

这使得更清楚的是,幻像参数实际上不携带运行时信息,只是控制编译器选择的实例.

不可否认,这非常难看.

™解决方案是让NEWTYPE包装包含的模型信息.实际上,这样的包装器已经在标准库中了:SumProduct.