「学习总结-Haskell-4」Haskell数据类型

来源:互联网 发布:js如何获取cookie参数 编辑:程序博客网 时间:2024/06/08 17:52

Haskell 数据类型

Table of Contents

  • 1 Haskell 基本知识
  • 2 Haskell 重要数据结构-List
  • 3 Haskell 常用数据结构
  • 4 Haskell 类型
    • 4.1 数据类型的查看与表示
      • 4.1.1 查看数据类型
      • 4.1.2 函数类型的表示
      • 4.1.3 基本数据类型
    • 4.2 类型类(Type class)
      • 4.2.1 为什么要有类型类
      • 4.2.2 常见类型类
    • 4.3 定义和使用新的类型
      • 4.3.1 定义一个新一数据类型
      • 4.3.2 使用新数据类型
      • 4.3.3 类别名(Type Synonyms)
      • 4.3.4 递归定义+类型变元(Type variable)
      • 4.3.5 让新类型成为类型类(Type class)的实例

1 Haskell 基本知识

2 Haskell 重要数据结构-List

3 Haskell 常用数据结构

4 Haskell 类型

4.1 数据类型的查看与表示

4.1.1 查看数据类型

ghci>:t 'a''a' :: Charghci>:t 33 :: Num a => aghci>:t 2.52.5 :: Fractional a => aghci>:t (1,2)(1,2) :: (Num t1, Num t) => (t, t1)ghci>:t [1,2][1,2] :: Num t => [t]ghci>:t "string""string" :: [Char]

4.1.2 函数类型的表示

一个特别的地方在函数类型

ghci>:t headhead :: [a] -> a

上面的head函数,类型是[a] -> a,意思是参数是List类型,其中List元素是a类型,返回的值是一个a类型。 这里的a叫做 类型变元(Type variable),它是个变量,取值可以是整数,字符,浮点数等等具体类型 另外,对于多个参数的函数,例如map

ghci>:t mapmap :: (a -> b) -> [a] -> [b]

这表示map有两个参数,第一个参数是一个函数,类型是a->b,即这个函数的类型是接受一个a类型的参数,返回一个b 类型的结果。第二个参数是一个List,元素类型是a。返回结果是一个List,元素类型是b。

之前我们定义一个函数时并没有指定一个函数的类型,这和C,C++,Java等语言定义函数时是不同的。之所以可以这样, 是因为Haskell的类型系统可以推断出函数的类型,所以即使你没给出函数的类型,在你输入一个错误的参数时, Haskell仍然会给出错误提示。

当然,我们也可以给出函数的类型,方法如下:

charReverse :: [Char] -> [Char]charReverse [] = []charReverse (x:xs) = charReverse xs ++ [x]
ghci>:t charReverse charReverse :: [Char] -> [Char]ghci>charReverse "asdf""fdsa"

4.1.3 基本数据类型

  • Int:代表有范围的整数
  • Integer: 代表没有范围的整数
  • Float,Double,Bool,Char: 你懂的
  • Tuple: 之前介绍过

4.2 类型类(Type class)

4.2.1 为什么要有类型类

我们看这样一个函数: ==,它有什么类型呢?检查一下。

ghci>:t (==)(==) :: Eq a => a -> a -> Bool

这个函数的类型是a -> a -> Bool.即接收两个相同类型的参数,返回Bool类型的值,例如

ghci>3 == 4Falseghci>3.1 == 3.2Falseghci>'a' == 'b'False

我们发现,这个函数并没有给出参数的具体类型,只说明了两个参数类型必须一样,那么 是不是只满足这一点就可以用==函数来判断相等了呢?当然不是。不然以后我们定义了新 类型,不告诉==什么才叫相等,==就能判断新类型相等了,那不是过于神奇了嘛。所以,必须 对==的参数类型加以限制,这就是前面那个Eq a 的作用了! 这里的Eq就是一个类型类(Type class),它并不是一个类,倒更像一个接口,Eq a对类型a 作了限制,表示类型a必须得是Eq的一个实例(instance)才行。而成为Eq的实例, 需要实现一些函数,这些函数告诉了==满足什么样的条件说明这两个元素是相等的。 所以,并不是所有类型的元素都可以做==的参数的,必须得是Eq实例的类型才可以。

4.2.2 常见类型类

  • Eq:一个类型成为它的实例,就可以用==比较这个类型的两个元素是不是相等了。
ghci>:t (==)(==) :: Eq a => a -> a -> Boolghci>3 == 4False
  • Ord:一个类型成为它的实例,就可以用>比较这个类型的两个元素的大小了。
ghci>:t (>)(>) :: Ord a => a -> a -> Boolghci>3 > 4Falseghci>3 == 4Falseghci>3 < 4Trueghci>3 /= 4True
ghci>:t compare compare :: Ord a => a -> a -> Orderingghci>3 `compare` 4LTghci>3 `compare` 3EQghci>4 `compare` 3GT
  • Show:一个类型成为它的实例,ghci就知道如何来显示它了。
ghci>show 3"3"ghci>show 4"4"ghci>show ['a','b']"\"ab\""ghci>show "ab""\"ab\""
  • Read:一个类型成为它的实例,ghci就可以把读取的东西当成这种类型的了。
ghci>:t readread :: Read a => String -> aghci>read "5" :: Int5ghci>read "5" :: Float5.0
  • Enum:一个类型成为它的实例,这个类型的元素就是可以依序列举出来。
  • Bounded:一个类型成为它的实例,这个类型的元素有上界和下界。
  • Num:一个类型成为它的实例,这个类型的元素有着和数字相似的行为。比如Int,Integer,Float,Double 都是Num的实例(instance)。
  • Floating:Float和Double是它的实例。
  • Integral:Int和Integer是它的实例。

4.3 定义和使用新的类型

4.3.1 定义一个新一数据类型

先看看在标准库里,数据类型是如何定义的

data Bool = False | Truedata Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647

我们看到了,数据类型可以使用 data 关键字来定义。 下面我们定义自己的数据类型。 假如我们要定义形状这个数据类型,形状可能是圆,也可能是矩形,我们可以这样定义

data Shape = Circle Float Float Float | Rectangle Float Float Float Float

这里的 Circle 和 Rectangle 我们称做值构造符(value constructor).这是什么意思呢? 我们看一下它们的类型:

ghci>:t Circle Circle :: Float -> Float -> Float -> Shapeghci>:t Rectangle Rectangle :: Float -> Float -> Float -> Float -> Shape

这么一看,居然和函数一样。为什么要这样表示呢? Shape是一个类型,一个类型总要有确定的值。就像Int类型有1,2,3这样的值。 那么怎么表示Shape的值呢?Shape的值无法用一个统一的形式来表示,因为圆需要三个值可以确定, 矩形则需要四个值。所以就引入值构造符(value constructor)这个概念,让不同形式的值统一起来。 例如:

ghci>:t (Circle 1 2 3)(Circle 1 2 3) :: Shapeghci>:t (Rectangle 1 2 3 4)(Rectangle 1 2 3 4) :: Shape

(Circle 1 2 3)和(Rectangle 1 2 3 4)都是Shape类型的值。

4.3.2 使用新数据类型

在定义了新类型后,就可以用这个新类型写一些函数了。

area :: Shape -> Floatarea (Circle _ _ r) = pi * r * rarea (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
ghci>area (Rectangle 1 2 3 4)4.0ghci>area (Circle 1 2 3)28.274334

当然,我们也可以再定义一个数据类型Point,再用Point构成Shape

data Point = Point Float Floatdata Shape = Circle Point Float | Rectangle Point Point

另外,如果我们在ghci里直接输入Circle 1 2 3是无法正常显示的,那么如何显示他们呢? 之前讲的类型类就有用了,要想显示,必须成为Show的实例,如何成为Show的实例呢? 只需要使用deriving

data Shape = Circle Float Float Float | Rectangle Float Float Float Float           deriving (Show)
ghci>Circle 1 2 3Circle 1.0 2.0 3.0

虽然我们没有具体定义如何成为Show的实例,即显示的方式,但是总算可以正常显示了, 这里是使用了默认的显示方式。

另外,在使用新数据类型时,还涉及一个问题,如果a = Circle 1 2 3.那么我们如何通过 a得到里面的值1 2 3呢?我们可以通过写三个函数来得到:

mrx (Circle x _ _ ) = xmry (Circle _ y _ ) = ymr (Circle _ _ vr ) = vr
ghci>let a = (Circle 1 2 3)ghci>mrx a1.0ghci>mry a2.0ghci>mr a3.0

但是这种方法实在太麻烦了,所以Haskell提供了另外一种方式。

data Shape = Circle {rx :: Float, ry :: Float, r :: Float} | Rectangle Float Float Float Float           deriving (Show)
ghci>let a = (Circle 1 2 3)ghci>rx a1.0ghci>ry a2.0ghci>r a3.0

这种方式在定义数据类型的同时,就定义了取得相应值的函数。

4.3.3 类别名(Type Synonyms)

可以通过下面的方式为[Char]起一个别名String,这样可以使类型的名字有较好的自我描述能力。

type String = [Char]

4.3.4 递归定义+类型变元(Type variable)

下面我们来定义一棵树,树的定义是递归的,我们可以这样定义

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)

这棵树是空树或者是一个结点值+左子树+右子树的形式。 这里的a是结点值的类型,即type variable,它可以代表Int,Float等等类型。如果不引入类型变元 的概念,那岂不是要为每种类型都定义一种树? 其中的Node是值构造符,它把结点值+左子树+右子树结合在了一起,构成一个Tree类型的值。

4.3.5 让新类型成为类型类(Type class)的实例

之前我们说明类型类的概念,比如Eq是一个类型类,新类型要想使用==函数,必须是 Eq的一个实例(instance),实例化的具体过程我们将在这里介绍。 假如定义了一种新类型

data TrafficLight = Red | Yellow | Green

此时,这种新类型必然无法使用==判断是否相等,因为==不知道何为相等,所以我们需要 告诉==何为相等,告诉的过程就是新类型成为Eq实例的过程。

instance Eq TrafficLight where  Red == Red = True  Yellow == Yellow = True  Green == Green = True  _ == _ = False

这就是实例化的过程,它告诉了==什么叫相等,什么叫不相等。

ghci>Red == RedTrueghci>Red == YellowFalse


原创粉丝点击