在类型族中编写函数多态

时间:2011-07-12 11:34:12

标签: haskell typeclass type-families

我昨天正在尝试使用类型系列,并遇到以下代码遇到障碍:

  {-# LANGUAGE TypeFamilies #-}

  class C a where
      type A a
      myLength :: A a -> Int

  instance C String where
      type A String = [String]
      myLength = length

  instance C Int where
      type A Int = [Int]
      myLength = length

  main = let a1 = [1,2,3]
             a2 = ["hello","world"]
         in print (myLength a1)
            >> print (myLength a2)

这里我有一个与C类相关的类型和一个计算相关类型长度的函数。但是上面的代码给了我这个错误:

 /tmp/type-families.hs:18:30:
     Couldn't match type `A a1' with `[a]'
     In the first argument of `myLength', namely `a1'
     In the first argument of `print', namely `(myLength a1)'
     In the first argument of `(>>)', namely `print (myLength a1)'
 /tmp/type-families.hs:19:30:
     Couldn't match type `A a2' with `[[Char]]'
     In the first argument of `myLength', namely `a2'
     In the first argument of `print', namely `(myLength a2)'
     In the second argument of `(>>)', namely `print (myLength a2)'
 Failed, modules loaded: none.

但是,如果我将“type”更改为“data”,则代码会编译并运行:

  {-# LANGUAGE TypeFamilies #-}

  class C a where
      data A a
      myLength :: A a -> Int

  instance C String where
      data A String = S [String]
      myLength (S a) = length a

  instance C Int where
      data A Int = I [Int]
      myLength (I a) = length a

  main = let a1 = I [1,2,3]
             a2 = S ["hello","world"]
             in
               print (myLength a1) >>
               print (myLength a2)

为什么“长度”在第一种情况下不能按预期工作?行“type A String ...”和“type A Int ...”指定类型“A a”是一个列表,因此myLength应分别具有以下类型:“myLength :: [String] - > Int “或”myLength :: [Int] - > Int“。

1 个答案:

答案 0 :(得分:14)

嗯。我们暂时忘记类型。

假设您有两个功能:

import qualified Data.IntMap as IM

a :: Int -> Float
a x = fromInteger (x * x) / 2

l :: Int -> String
l x = fromMaybe "" $ IM.lookup x im
  where im = IM.fromList -- etc...

假设存在一些您关心的值n :: Int。仅给出a n的值,您如何找到l n的值?当然,你没有。

这有什么关系?好吧,myLength的类型是A a -> Int,其中A a是将“类型函数”A应用于某种类型a的结果。但是,myLength是类型类的一部分,类参数a用于选择要使用的myLength实现。因此,给定某些特定类型B 的值,将myLength应用于它会提供B -> Int类型,其中B ~ A a并且您需要知道a以查找myLength的实施情况。仅给出A a的值,您如何找到a的值?当然,你没有。

您可以合理地反对在此处的代码中,函数A 可逆的,与我之前示例中的a函数不同。这是事实,但由于 open world 假设涉及类型类,编译器无法对此做任何事情;理论上,你的模块可以由另一个定义自己实例的模块导入,例如:

instance C Bool where
    type A Bool = [String]

傻?是。有效代码?也是的。

在许多情况下,在Haskell中使用构造函数用于创建平凡的内射函数:构造函数引入了一个新实体,该实体仅由它给出的参数唯一定义,使得恢复变得简单原始价值观。这正是您的代码的两个版本之间的差异;数据系列通过为每个参数定义一个新的,不同的类型,使类型函数可逆。