在类型族中编写函数多态

Dee*_*ech 13 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)
Run Code Online (Sandbox Code Playgroud)

这里我有一个与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.
Run Code Online (Sandbox Code Playgroud)

但是,如果我将"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)
Run Code Online (Sandbox Code Playgroud)

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

C. *_*ann 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...
Run Code Online (Sandbox Code Playgroud)

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

这有什么关系?嗯,类型myLengthA a -> Int,A a将"类型函数" A应用于某种类型的结果a.但是,myLength作为类型类的一部分,class参数a用于选择myLength要使用的实现.所以,给定一些特定类型B的值,应用myLength它给出了一种类型B -> Int,在哪里B ~ A a,你需要知道a为了查找实现myLength.只给出价值A a,你如何找到价值a?当然,你没有.

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

instance C Bool where
    type A Bool = [String]
Run Code Online (Sandbox Code Playgroud)

愚蠢?是.有效代码?也是的.

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