【问题标题】:Get a list of the instances in a type class in Haskell在 Haskell 中获取类型类中的实例列表
【发布时间】:2011-03-22 19:34:05
【问题描述】:

有没有办法以编程方式获取类型类的实例列表?

让我印象深刻的是编译器必须知道这些信息才能进行类型检查和编译代码,所以有什么方法可以告诉编译器:嘿,你知道那个类的那些实例,请把它们列一个正确的列表这里(作为字符串或它们的某种表示形式)。

【问题讨论】:

  • 其实编译器并不知道定义了哪些实例。它唯一能做的就是检查一个类型是否是一个类型类的实例。

标签: haskell introspection


【解决方案1】:

您可以使用 Template Haskell 为给定类型类生成范围内的实例。

import Language.Haskell.TH

-- get a list of instances
getInstances :: Name -> Q [ClassInstance]
getInstances typ = do
  ClassI _ instances <- reify typ
  return instances

-- convert the list of instances into an Exp so they can be displayed in GHCi
showInstances :: Name -> Q Exp
showInstances typ = do
  ins <- getInstances typ
  return . LitE . stringL $ show ins

在 GHCi 中运行:

*Main> $(showInstances ''Num)
"[ClassInstance {ci_dfun = GHC.Num.$fNumInteger, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Integer.Type.Integer]},ClassInstance {ci_dfun = GHC.Num.$fNumInt, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Int]},ClassInstance {ci_dfun = GHC.Float.$fNumFloat, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Float]},ClassInstance {ci_dfun = GHC.Float.$fNumDouble, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Double]}]"

另一种有用的技术是使用 GHCi 显示给定类型类范围内的所有实例。

Prelude> :info Num
class (Eq a, Show a) => Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in GHC.Num
instance Num Integer -- Defined in GHC.Num
instance Num Int -- Defined in GHC.Num
instance Num Float -- Defined in GHC.Float
instance Num Double -- Defined in GHC.Float

编辑:要知道的重要一点是,编译器只知道任何给定模块(或 ghci 提示符等)范围内的类型类。因此,如果您在没有导入的情况下调用 showInstances TH 函数,您将只能从 Prelude 中获取实例。如果您在范围内有其他模块,例如Data.Word,那么您也会看到所有这些实例。

【讨论】:

  • 看来这只适用于 GHC 7 和 TH 2.5。在 TH 2.4.0.1 中尝试这样做似乎并没有给我想要的东西。那么这是一个新功能吗?
  • 我正在运行 GHC 7.6.3。这是答案的更新:[ClassInstance] 现在是[InstanceDec]。另外,我需要在 GHCi 中运行 :set -XTemplateHaskell 才能复制示例。
【解决方案2】:

查看模板 haskell 文档:http://hackage.haskell.org/packages/archive/template-haskell/2.5.0.0/doc/html/Language-Haskell-TH.html

使用reify,您可以获得一个信息记录,对于一个类,它包括它的实例列表。也可以直接使用isClassInstanceclassInstances

【讨论】:

    【解决方案3】:

    一旦你得到像这样的实例声明,这将会遇到很多问题

    instance Eq a => Eq [a] where
        [] == [] = True
        (x:xs) == (y:ys) = x == y && xs == ys
        _ == _ = False
    

    instance (Eq a,Eq b) => Eq (a,b) where
        (a1,b1) == (a2,b2) = a1 == a2 && b1 == b2
    

    以及单个具体实例(例如instance Eq Bool)。

    您将获得Eq - Bool[Bool][[Bool]][[[Bool]]] 等、(Bool,Bool)((Bool,Bool),Bool)(((Bool,Bool),Bool),Bool) 等的无限实例列表使用这些的各种组合,例如([((Bool,[Bool]),Bool)],Bool) 等等。目前尚不清楚如何在String 中表示这些;即使是TypeRep 的列表也需要一些非常聪明的枚举。

    编译器可以(尝试)推断一个类型是否是任何给定类型的Eq 的实例,但它不会读取范围内的所有实例声明,然后才开始推断所有可能的实例,因为那永远不会完成!

    当然,重要的问题是,您需要这个做什么?

    【讨论】:

      【解决方案4】:

      我想,这是不可能的。我向你解释了类型类的实现(对于 GHC),从中你可以看到,编译器不需要知道哪些类型是类型类的实例。它只需要知道特定类型是否为实例。

      类型类将被转换为数据类型。以Eq为例:

      class Eq a where
        (==),(/=) :: a -> a -> Bool
      

      typeclass 将被翻译成一种字典,包含它的所有功能:

      data Eq a = Eq {
          (==) :: a -> a -> Bool,
          (/=) :: a -> a -> Bool
        }
      

      然后将每个类型类约束转换为包含字典的额外参数:

      elem :: Eq a => a -> [a] -> Bool
      elem _ [] = False
      elem a (x:xs) | x == a    = True
                    | otherwise = elem a xs
      

      变成:

      elem :: Eq a -> a -> [a] -> Bool
      elem _  _ [] = False
      elem eq a (x:xs) | (==) eq x a = True
                       | otherwise   = elem eq a xs
      

      重要的是,字典将在运行时传递。想象一下,您的项目包含许多模块。 GHC 不必检查所有模块的实例,它只需要查找实例是否在任何地方定义。

      但是,如果您有可用的源,我想实例的旧式grep 就足够了。

      【讨论】:

      • 不完全正确。编译器只需要知道特定类型是否是实例,但是编译器可以随时枚举范围内的所有实例并生成所需的列表。例如,如果您使用 :info Num,GHCi 会执行此操作。
      【解决方案5】:

      不可能对现有类自动执行此操作。对于您自己的类及其实例,您可以这样做。您需要通过 Template Haskell(或者可能是准引用)声明所有内容,它会自动生成一些奇怪的数据结构来编码声明的实例。定义奇怪的数据结构并让 Template Haskell 执行此操作是留给任何有使用它们的人的细节。

      也许您可以在构建中添加一些 Template Haskell 或其他魔法,以将所有源文件作为运行时可用的文本包含在内(参见 program quine)。然后你的程序会'grep本身'......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-20
        • 2015-09-07
        • 2016-08-12
        • 1970-01-01
        • 2016-12-05
        • 2015-02-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多