【问题标题】:When using MultiParamTypeClasses, do you need to use every type in every class function使用MultiParamTypeClasses时,是否需要在每个类函数中使用每个类型
【发布时间】:2013-11-20 19:06:57
【问题描述】:

当我使用 MultiParamTypeClasses 时,我可以创建忽略类型参数之一的类函数(即,像下面的“identity”)。

{-# 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

(这是一个精简版,当然在完整的程序中还有其他函数会使用“b”)。

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

main = do
    putStrLn (show (identity::Int))

导致“(Test Int b0)没有因使用'identity'而产生的实例。

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

【问题讨论】:

    标签: haskell


    【解决方案1】:

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

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

    在某些类似情况下有效的修复程序(但不适用于您的情况):

    • 使未确定类型变量在功能上依赖其他变量之一。

      {-# LANGUAGE FunctionalDependencies #-}
      class Group_FD g p | g->p where
        identity :: g
      

      这可以像这样使用

      data Nat = One | Succ Nat
      
      instance Group_FD Nat Mult where
        identity = One
      
      instance Group_FD Int Add where
        identity = 0
      

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

    • 为仅依赖于该参数的方法定义一个只有一个参数的单独类。然后使这个类成为另一个类的约束(“超类”),以“导入”方法:

      class Identity i where
        identity :: i
      class (Identity i) => Test i y
      

      这对您的应用程序一点用处都没有,因为您希望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
    

    然后你可以像这样使用它

    GHCi> 身份添加 :: Int
    0
    GHCi> 标识 Mult :: Int
    1

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

    {-# LANGUAGE EmptyDataDecls #-}
    data Add
    data Mult
    

    GHCi> 标识(未定义 :: Add) :: Int
    0
    GHCi> 身份(未定义 :: Mult) :: Int
    1

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

    诚然,这很丑。

    正确的™ 解决方案是制作新类型的包装器来包含幻像信息。事实上,这些包装器已经在标准库中:SumProduct

    【讨论】:

    • 查看我的答案以了解可以使用此类定义的方式,而无需更改类定义。但是,同意,这种用法毫无用处。
    • 也许你在谈论幻像参数时也应该提到代理?
    • @bennofs:老实说,我认为代理比代码中的 undefined 参数更丑陋。
    • 我选择了这个作为正确答案,因为它提供了很好的实用建议,虽然我认为 bennofs 的回答也为讨论增加了一些重要信息,可惜我不能分配功劳。我仍然很好奇代理的东西是什么......
    • @jamshidh:您可以upvote bennofs 的回答,这也给予了肯定(10 分,而接受为 15 分)。 — 至于 Proxy 的东西……你添加了另一个 data GroupSelector p = SelectGroup,它带有一个普通的构造函数,但也带有一个类型参数,因此构造函数具有多态类型。然后你可以做identity (SelectGroup :: GroupSelector Add) :: Int。这与非空 AddMult 数据类型的结果几乎相同,但方式更加迂回。 (代理确实有一些不完全不合理的应用,但这种情况很少见。)
    【解决方案2】:

    如果您使用FlexibleInstances,则可以更改实例定义以便可以使用身份:

    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE FlexibleInstances #-}
    
    data Add = Add
    data Mul = Mul
    
    class Test a b where
        identity::a
    
    instance Test Int b where
        identity = 0
    
    main :: IO ()
    main = do
      print $ (identity :: Int)
    

    因为实例可以在其他文件中定义,然后只有该文件需要使用 FlexibleInstances,所以 GHC 通常不能禁止不使用所有类型变量的类声明。

    如果您想要不同b's 的实例,则必须使用FunctionalDependencies

    【讨论】:

      猜你喜欢
      • 2019-06-10
      • 2019-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-07
      • 1970-01-01
      • 2021-10-17
      相关资源
      最近更新 更多