Haskell is a lazy, functional programming language created in the late 1980’s by a committee of academics. There were a plethora of lazy functional languages around, everyone had their favorite, and it was hard to communicate ideas. So a bunch of people got together and designed a new language, taking some of the best ideas from existing languages (and a few new ideas of their own). Haskell was born.
Haskell的特点
函数式
- 函数在haskell中是一等公民
haskell中的函数可以被赋值也可以被传递。
- haskell更注重表达式结果的计算, 而不是按照步骤逐一执行指令。
纯函数
- No mutation!, 任何东西都是不可变的
haskell中的变量、数据结构均不可变。
- 零副作用(副作用: 比如更新全局变量, 打印到屏幕, 写入文件)
- 相同输入, 相同输出
纯函数的好处:
并行计算, 对于两个没有任何副作用的表达式的计算, 执行顺序谁先谁后是不会影响结果的, 因此天然适合并行计算
代码更不容易出bug!
惰性求值
在haskell中, 一条表达式除非需要用到它的结果, 否则不会进行计算。
静态类型
Every Haskell expression has a type, and types are all checked at compile-time. Programs with type errors will not even compile, much less run.
Haskell的声明和变量
类型声明
每一个haskell变量都有一个不可变的数据类型, 声明方式使用::。
如
x :: Int
x = 3
-- or
-- x :: Int = 3从上面我们可以学到
- 类型声明的形式为
name :: type - 赋值使用
= - Haskell的类型声明和赋值可以分开进行
- 使用
--进行单行注释
haskell的变量是不可变的
x :: Integer = 3
x = 4以上代码将报错: error: Multiple declarations of ‘x’
虽然叫"变量", 但它是不可变的, 它只是值的一种名称。
基础数据类型
Int
Int是haskell中的固定长度(精度)整形, 注意在不同计算机中Int的长度可能不同。
Integer
Integer是无限长度的整形。
Float、Double
Float、Double分别是单、双精度的浮点数类型。
-- Double-precision floating point
d1, d2 :: Double
d1 = 4.5387
d2 = 6.2831e-4Bool
布尔值, True / False
Char
unicode字符。
c1, c2, c3 :: Char
c1 = 'x'
c2 = 'Ø'
c3 = '宋'注意Char字面量必须使用单引号, 跟c语言类似, 双引号是用来表示字符串字面量的, 关于字符串后面会讲到。
Haskell的运算符
算术运算符
ghci> 3 + 2
5
ghci> 19 - 27
-8
ghci> 2.35 * 8.6
20.21
ghci> 8.7 / 3.1
2.8064516129032255
ghci> mod 19 3
1
ghci> 19 `mod` 3
1
ghci> 7 ^ 222
40903915558252355961885564235233827390274916808670721972378015470397485101670867316479654900404204284975885535566242786061025593172032118590958393531614633803778811048702555046770492868049
ghci> (-3) * (-7)
21注意, 以上是在haskell的交互式命令行(
gchi)上演示的。 这没什么令人困惑的, 相比于将结果保存在变量中, 然后打印, 这样的方式我觉得更加简洁。
一些注意点:
在上面mod有两种使用方法, 分别是前缀和中缀。 本质上mod是haskell中一个函数, 所以调用方式为
funcName arg1 arg2 ...。 而中缀的形式是因为haskell的一个语法糖, 对于两个参数的函数, 可以使用反引号包裹函数名, 便能中缀调用。 表达式的负数一般会用括号包裹以表达它是一个整体, 否则haskell可能会认为它是减号, 如·
-1 + -2将引发报错算术表达式的两个值必须是同一数字类型, haskell不会自动帮你做隐式类型转换
比如
> a :: Integer = 2;
> b :: Float = 3.14;
> a * b
Couldn't match expected type ‘Integer’ with actual type ‘Float’可以使用fromIntegral将整形转为任何其他数字类型, 如(fromIntegral a) * b即可正常运行。
还可以使用round、ceil、floor这些数学函数将浮点类型转换为整形。
/只能用于浮点类型, 对于整形除法需要使用div函数
比较运算符
ghci> 2 > 1
True
ghci> 1 < 2
True
ghci> 2 >= 1
True
ghci> 1 <= 2
True
ghci> 1.5 == 1.5
True到此为止, 一切都和C很像不是吗。
注意比较运算同样只能相同类型的值进行使用。
逻辑运算符
ghci> True && False
False
ghci> True || False
True
ghci> not True
FalseHaskell的函数定义
函数的调用方式
funcName arg1 arg2 ...函数名在前, 参数依次按空格分隔排列在后。
定义函数的类型
函数的类型定义方式如:
sum :: Integer -> Integer --> Integer这段定义的含义是sum函数有两个参数, 第一个参数类型为Integer, 第二个参数类型为Integer, 返回值类型为Integer。
haskell基础函数的定义正如Arg1Type -> Arg2Type -> ... -> ResultType
定义函数的实现
以下是一个斐波那契数列的简单实现(并不完全正确, 参数没有考虑负数):
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib(n - 1) + fib(n - 2)一个函数可以定义多个实现, fib调用时会按照顺序, 将实参与每个函数实现的参数进行匹配, 并选择第一个匹配成功的实现。
如fib 0 = 0就是匹配参数为0的情况, 返回值为0.
fib n = fib(n - 1) + fib(n - 2), 这里使用了变量作为函数的参数, 这将匹配任何内容。
除了参数匹配, 还有另一种方式能表达函数内的控制流, 那就是守卫*guards*。
比如:
fib :: Integer -> Integer
fib n
| n <= 0 = 0
| n == 1 = 1
| otherwise = fib(n - 1) + fib(n - 2)格式上为
funName arg1Match arg2Match
| expression1 = value1
| expression1 = value2
| otherwise = value3当守卫的表达式为真时, 将返回对应的值; 否则将进行下一个守卫。
列表(list)
列表?数组?
一般认为,数组和列表的区别在于, 数组将所有元素存储在一块连续的内存空间中, 而列表则没有这个保证。
由于数组内存连续的特性, 使用首地址 + 偏移量访问是非常快速的, 就如c中的数组。
按以上标准来看, Haskell中只有列表没有数组, 因为Haskell的列表实际上是一个链表。
但我习惯了把有序序列都叫成数组, 所以我在某个地方笔误时, 这是很有可能的。
列表的基础操作
声明列表类型
list :: [Integer]如上声明了一个整形列表, 注意haskell中的列表每一个元素都必须是相同类型。
声明一个空列表
使用[]即代表了一个空列表。
list = []构建列表
使用冒号运算符, 将一个元素和一个列表合并为一个新列表, 元素会在最前面。
ghci> 1 : []
[1]
ghci> 1 : 2 : 3 : []
[1,2,3]
ghci> -- 上面这种写法等价于下面这种
ghci> 1 : (2 : (3 : []))
[1,2,3]
ghci> -- 冒号运算符的语法糖
ghci> [1, 2, 3] == 1 : 2 : 3 : []
True模式匹配(pattern matching)
ghci> x : xs = [1, 2, 3]
ghci> x
1
ghci> xs
[2,3]
ghci> a : b : [] = [1, 2]
ghci> a
1
ghci> [a, b, c] = [1, 2, 3]
ghci> b
2这有点像其他语言中的解构不是吗(或者反过来), 所以有时候我可能会把它叫成解构。
当使用两个变量时x : xs = [1, 2, 3, 4], 将匹配得到第一项和列表剩余部分。 注意x : xs = []将会引发错误。
当时使用若干个变量 + 一个空数组时, 如a : b : c : []或[a, b, c], 这要求被匹配的列表有和变量数量相同的长度。
列表与函数
仍然以一个例子来展开
sum :: [Integer] -> Integer
sum [] = 0
sum (x:xs) = x + sum xs这是一个对Integer列表求和的函数, 让我们看看能学到什么~
- 参数值匹配对列表是有效的, haskell会比较它们的值
ghci> [1, 2] == [1, 2]
True- 列表的模式匹配和函数的参数匹配结合非常有用
fun :: [Integer] -> Integer
fun [x] = 1
fun [a, b] = 2
func list = 3以上这个函数, 在列表有一个元素时返回1, 两个元素时返回2, 否则返回3。
看到了吧, 当模式匹配失败时, 会继续进行后面的匹配。
元组 or pair?
元组和列表的不同在于, 元组中每个元素的类型是可以不同的。
p :: (Int, Char)
p = (1, 'a')以上声明了一个长度为2的元组, 第一个元素类型为Int, 第二个元素类型为Char
Haskell also has triples, quadruples, … but you should never use them. As we’ll see next week, there are much better ways to package three or more pieces of information together.
对于长度为2的元组, 或者称为pair, 我们可以使用fst和snd获取第一个和第二个元素的值。
ghci> p = (1, 'a')
ghci> fst p
1
ghci> snd p
'a'元组也可以进行模式匹配
ghci> (a, _) = (1, 'a')
ghci> a
1