haskell 类型类

类型类

类型类定义了一系列函数,这些函数对于不同类型的值使用不同的函数实现。它和其他语言的接口和多态方法有些类似。	
  • 类型类使用 class 关键字来定义,跟在 class 之后的 BasicEq 是这个类型类的名字,之后的 a 是这个类型类的实例类型(instance type)
  • BasicEq 使用类型变量 a 来表示实例类型,说明它并不将这个类型类限定于某个类型:任何一个类型,只要它实现了这个类型类中定义的函数,那么它就是这个类型类的实例类型。
class BasicEq a where
    isEqual :: a -> a -> Bool
    isEqual x y = not (isNotEqual x y)
    isNotEqual :: a -> a -> Bool
    isNotEqual x y = not (isEqual x y)
  • 定义一个类型为某个类型类的实例,指的就是,为某个类型实现给定类型类所声明的全部函数。
  • Bool 类型作为 BasicEq 的实例类型,实现了 isEqual 函数,只要定义 isEqual 函数,就可以“免费”得到 isNotEqual
instance BasicEq Bool where
    isEqual False False = True
    isEqual True  True  = True
    isEqual _     _     = False

内置类型类

  • Show
  • Read
Prelude> :type read
read :: Read a => String -> a
Prelude> (read "3")::Int
3
Prelude> (read "[1999,2010,2012]")::[Int]    -- 将字符串转换成列表
[1999,2010,2012]

read 函数返回正确类型的值,必须给它指示正确的类型

  • Eq
  • Ord,所有 Ord 实例都可以使用 Data.List.sort 来排序。

自动派生

  • 简单的数据类型, Haskell 编译器可以自动将类型派生(derivation)为 Read 、 Show 、 Bounded 、 Enum 、 Eq 和 Ord 的实例。
data Color = Red | Green | Blue
    deriving (Read, Show, Eq, Ord)

使用类型别名创建实例

  • String 是 [Char] 的别名,因此它的类型是 [a],并用 Char 替换了类型变量 a。 根据 Haskell 98的规则,我们在声明实例的时候不能用具体类型替代类型变量。 也就是说,我们可以给 [a] 声明实例,但给 [Char] 不行。
-- file: ch06/JSONClass.hs
instance JSON String where
    toJValue               = JString
    fromJValue (JString s) = Right s
    fromJValue _           = Left "not a JSON string"

第一,Haskell 98不允许类型别名

{-# LANGUAGE TypeSynonymInstances #-}

第二,Haskell 98不允许[Char]这种形式的类型

{-# LANGUAGE FlexibleInstances #-}

重叠实例(Overlapping instances)

instance (JSON a) => JSON [a] where
    toJValue = undefined
    fromJValue = undefined
instance (JSON a) => JSON [(String, a)] where
    toJValue = undefined
    fromJValue = undefined
  • GHC 支持另外一个有用的语言扩展,OverlappingInstances,它解决了重叠实例带来的问题。 如果存在重叠实例,编译器会选择最相关的(specific)那一个。
{-# LANGUAGE  OverlappingInstances #-}
  • 通过newtype包装
-- file: ch06/JAry.hs
newtype JAry a = JAry {
      fromJAry :: [a]
    } deriving (Eq, Ord, Show)
newtype JObj a = JObj {
      fromJObj :: [(String, a)]
    } deriving (Eq, Ord, Show)

帮助GHC区分即用来表示JSON数组的类型 [a] ;以及用来表示JSON对象的类型 [(String, [a])]

-- file: ch06/JSONClassExport2.hs
module JSONClass
    (
      JAry(fromJAry)
    , jary
    ) where  
jary :: [a] -> JAry a
jary = JAry

通常,在导出一个 newtype 的时候,为了让该类型的细节对外透明,我们 不会 导出这个类型的值构造器,而是定义一个函数来替我们调用值构造器。

second

-- 理解Control.Arrow 的second
:type second
second :: Arrow a => a b c -> a (d, b) (d, c)
-- 其中a 是箭头的实例就是(->)
-- 所以变成了
-- second :: (->) b c -> (->) (d , b ) ( d , c)
-- second :: ( b -> c ) -> (d , b ) -> (d ,c )
-- second 接受一个 b 到 c 的函数,以及一个pair(d,b)
-- 然后对pair的第二个元素进行函数转变,产生新的pair(d,c)

(,) (,,)

:type (,)
(,) :: a -> b -> (a, b)
:type (,,)
(,,) :: a -> b -> c -> (a, b, c)

单一同态限定(monomorphism restriction)

-- file: ch06/Monomorphism.hs
-- error
-- myShow = show
myShow2 value = show value

myShow3 :: (Show a) => a -> String
myShow3 = show

上面的定义表明,如果 GHC 报告单一同态限制错误,我们有三个简单的方法来处理。

  • 显式声明函数参数,而不是隐性。
  • 显式定义类型签名,而不是依靠编译器去推导。
  • 不改代码,编译模块的时候用上 NoMonomorphismRestriction 语言扩展。它取消了单一同态限制。