1. Haskell的类型系统

  Haskell的类型有3个特性:

type strong(强类型)

type static (静态类型)

auto-inferred (自动推导类型)

  1.1 强类型

  1.   强类型只会执行well typed的类型,不执行ill typed。
  2.   强类型不会进行类型自动转换, 必要时显式地使用类型转换函数。
  3.   强类型可以检测类型错误的bug。

  1.2 静态类型

    编译器在编译期(非执行期)就可识别变量或表达式的类型。编译器会拒绝执行不正确的表达式:

1 GHCi> True && "False"
2 
3 <interactive>:8:9:
4     Couldn't match expected type ‘Bool’ with actual type ‘[Char]’
5     In the second argument of ‘(&&)?? namely ‘"False"’
6     In the expression: True && "False"
7     In an equation for ‘it’: it = True && "False"
8 (0.03 secs, 8,416,164 bytes)

     Haskell提供的type class机制安全,方便,实用,并提供大部分动态类型的优点,也提供了一部分对全动态类型(truly dynamic type)编程的支持。

  1.3 类型推导

    Haskell编译器可以自动推断出几乎所有表达式的类型。

  1.4 正确理解类型系统

    上面的强类型和静态类型使Haskell更安全,类型推导是Haskell更精炼,简洁。因为这相当于在做类型检查工作,提前做了调试工作。

  1.5 常用的基本类型

    Char: 单个Unicode字符

    Bool:布尔类型 True,False 不是等同于1和0.

    Int: 带符号的定长整数。 Haskell中不少于28 bit。

    Integer: 不限长度的带符号整数。 需要更多地内存和计算量。不会造成溢出。 更可靠。

    Double: 浮点数。通常是64 bit。Haskell不推荐使用Float类型, 这会使计算较慢。

    特别说明:  :: 符号

    第一表示类型,第二表示类型签名。例如: exp::T 及表示exp的类型是T,::T就是表达式exp的类型签名。如果表达式未显式地指明类型,那么他的类型就会通过自动推导决定:

1 GHCi> :type 'a'
2 'a' :: Char
3 GHCi> 'a'
4 'a'
5 (0.00 secs, 0 bytes)
6 GHCi> 'a'::Char
7 'a'
8 (0.00 secs, 0 bytes)

 2. 调用函数

  先写函数名,再写参数列表, 用空格隔开。 

  函数的优先级要高于操作符。(第10行)

1 True
 2 GHCi> odd 6
 3 False
 4 GHCi> compare 3 4
 5 LT
 6 GHCi> compare 3 3
 7 EQ
 8 GHCi> compare 5 3
 9 GT
10 GHCi> compare 2 3 == LT
11 True

  为了便于可读, 必要的()也要加上。

1 GHCi> compare (sqrt 3) (sqrt 6)
 2 LT
 3 GHCi> compare sqrt 3 sqrt 6
 4 
 5 <interactive>:21:1:
 6     Couldn't match expected type ‘(Double -> Double) -> Integer -> t’
 7                 with actual type ?甇rdering’
 8     Relevant bindings include it :: t (bound at <interactive>:21:1)
 9     The function ‘compare’ is applied to four arguments,
10     but its type ‘(a0 -> a0) -> (a0 -> a0) -> Ordering’ has only two
11     In the expression: compare sqrt 3 sqrt 6
12     In an equation for ‘it’: it = compare sqrt 3 sqrt 6

 3. 复合数据类型: 列表和元组

  复合类型是由其它类型构建而成。如 String == [Char]

  head函数取列表的第1个元素, tail函数取除第1个元素的后续元素。

1 GHCi> head ['a','b','c']
 2 'a'
 3 GHCi> head "list"
 4 'l'
 5 GHCi> head []
 6 *** Exception: Prelude.head: empty list
 7 GHCi> tail [1,2,3,4]
 8 [2,3,4]
 9 GHCi> tail "list"
10 "ist"
11 GHCi> tail []
12 *** Exception: Prelude.tail: empty list

类型多态的。

  当需要编写带有多态类型的code,需要使用类型变量。这些类型变量以小写字母开头,作为一个占位符, 最终被一个具体类型替代。

  比如 [a]表示类型为a的列表, [Int]表示一个包含Int类型值的列表。

  也可以递归替换。[[Int]]是一个包含[Int]类型值的列表。

  元组

  如果我们需要保存不同类型的数值时,就可以使用元组。

  列表与元组的区别

  列表可以任意长度, 且只包含相同类型, 用[]包含。

  元组只能固定长度, 可以包含不同类型, 用()包含。

1 GHCi> (4,"Hello",(12,True),['a','b'])
2 (4,"Hello",(12,True),"ab")
3 GHCi> ()
4 ()

  Haskell有一个特殊的元组(),即包含0个元素的元组, 相当于C中的void。

  通常用元组中元素的数量称呼元组的前缀。 比如 5-元组 称呼 包含5个元素的元组。 不可以创建包含一个元素的元组。 (1-元组)

  元组的类型

  只有当元组中元素的数量,位置,类型都相同时,才称为相同的元组类型:

1 GHCi> :t (False,'a')
2 (False,'a') :: (Bool, Char)
3 GHCi> :t (True, 'b')
4 (True, 'b') :: (Bool, Char)

  元组的用途

  当函数需要返回多个值时。

  当需要使用定长容器,又不需要自定义类型。

4. 处理列表和元组的函数

  take n l : 返回一个包含列表l中前n个元素的列表

  drop n l : 返回一个丢弃列表l中前n个元素的列表

 1 GHCi> take 2 [1,2,3,4,5] 2 [1,2] 3 GHCi> drop 3 [1,2,3,4,5] 4

  fst (...) : 返回元组中的第1个元素

  snd(...) : 返回元组中的第2个元素

1 GHCi> fst ([1,2,4],True) 2 [1,2,4] 3 GHCi> snd ([1,2,4],True) 4

  4.1 将表达式传给函数

    要恰当的使用()传递参数

1 GHCi> head (drop 4 "dfrtu")
2 'u'
3 GHCi> head "u"
4 'u'

  4.2 函数类型

  可以用:type命令查看函数类型, 简写:t 

1 GHCi> :type lines 2

  符号 -> 表示“映射到”, 也可以认为是返回。

  String -> [String]: 表示lines函数定义了一个String到[String]的函数映射。

  lines函数的类型签名表示, lines可以用单个字符串作为参数, 返回一个包含多个字符串的列表:

1 GHCi> lines "the quick\nbrown fox\njumps"
2 ["the quick","brown fox","jumps"]
3 GHCi> lines ("dfetrt")
4 ["dfetrt"]
5 GHCi> lines ("dfetrt" ++ "\nhere")
6 ["dfetrt","here"]

5. 纯度

  带副作用的函数称为不纯函数(impure), 不带副作用的函数称为纯函数(pure)。

  副作用指的是, 函数的行为受全局状态的影响。比如: 

  一个函数要读取并返回某个全局变量, 如果程序中其它代码可以修改全局变量,那么这个函数的返回值取决于这个全局变量某一时刻的返回值。 我们说这个函数带有副作用。

  副作用本质上就是一个函数不可见的(invisiable)输入或输出。 Haskell的函数默认都是无副作用的: 函数的结果只取决于显示传入的参数。

  不纯函数的类型签名都以IO开头:

1 GHCi> :type readFile 2

 6. Haskell源码及简单函数的定义

  Haskell源码以.hs为后缀。 在ghci中定义函数和Haskell源码里定义有些差异。这里创建一个最简单的add函数;

  使用:cd,使ghci切换到add.hs的当前目录;

  使用:load(省略为:l)加载add.hs文件;

  OK,现在可以调用add函数了:

1 GHCi> :cd F:\Haskell\projs
2 Warning: changing directory causes all loaded modules to be unloaded,
3 because the search path has changed.
4 GHCi> :l add.hs
5 [1 of 1] Compiling Main             ( add.hs, interpreted )
6 Ok, modules loaded: Main.
7 GHCi> add 4 5
8 9

  add a b = a + b 函数说明:

  add a b是函数名和函数参数列表, a + b是函数体, = 表示将左边的名字定义为右边的表达式。

  表达式的结果就相当于函数的返回值, Haskell函数的定义, 就是一个表达式而已,不必陈述return语句。

  6.1 变量

    Haskell变量是与表达式相关量的,可以认为是表达式的别名, 可以替换,就像函数式一样。

    所以,如果我们做变量的多次赋值,Haskell会不允许。 比如Assign.hs中, x = 10;x = 11 :

1 GHCi> :l Assign.hs
2 
3 Assign.hs:2:1:
4     Multiple declarations of ‘x’
5     Declared at: Assign.hs:1:1
6                  Assign.hs:2:1
7 [1 of 1] Compiling Main             ( Assign.hs, interpreted )
8 Failed, modules loaded: none.

  6.2 条件求值

    利用Haskell的if then else 语句来实现自己的drop函数功能(myDrop.hs):

1 myDrop n xs = if n <= 0 || null xs
2     then xs
3     else myDrop (n-1) (tail xs)

分支的类型必须相同,且else不可或缺。

    :l myDrop.hs看下结果先:

1 GHCi> drop 2 "foobar"
 2 "obar"
 3 GHCi> drop 4 [1,2]
 4 []
 5 GHCi> drop 0 [1,2]
 6 [1,2]
 7 GHCi> drop (-2) "foo"
 8 "foo"
 9 GHCi> :l myDrop.hs
10 [1 of 1] Compiling Main             ( myDrop.hs, interpreted )
11 Ok, modules loaded: Main.
12 GHCi> myDrop 2 "foobar"
13 "obar"
14 GHCi> myDrop 4 [1,2]
15 []
16 GHCi> myDrop 0 [1,2]
17 [1,2]
18 GHCi> myDrop (-2) "foo"
19 "foo"

  6.3 通过实例了解求值

    先来一个通过取模运算判断奇数的例子(isOdd.hs):

    非严格求值: 即表达式被求值的时刻。 比如 isOdd (1+2): 并不是先计算1+2的值, 而是将此表达式带入isOdd函数中,在需要求值时才会计算。

    用于追踪未求值表达式的记录称为(thunk)。

    非严格求值通常被称为惰性求值, 实际上有差别。

    从myDrop.hs的递归函数发现, 函数的返回值可能是一个块。

7. Haskell的多态

  7.1 参数多态

    我们通过last函数来说明参数多态

1 GHCi> last [1,2,3,4,5]
2 5
3 GHCi> last "abc"
4 'c'
5 GHCi> :t last
6 last :: [a] -> a

    [a]是last函数的参数,a是返回值,->表示右关联。即是传入一个类型变量为a的列表, 返回一个类型变量为a的元素。具体a是什么类型,Haskell不关心。

    即是参数多态。

    Haskell不支持强制多态(像整型自动转化成浮点型那样)

  7.2 多参数函数的类型

    take函数接收1个整型,1个列表,共2个参数:

1 GHCi> :t take 2

    从右关联可以看出, take接收Int,类型变量为a的列表, 返回类型变量为a的列表。

8. 纯度

  Haskell默认使用纯函数。这意味着它不会做类似读取文件,访问网络,返回当前时间等操作。