Haskell 介绍

基本介绍

Haskell 是一种标准化的,通用的纯函数编程语言,有非限定语义和强静态类型。它的命名源自美国逻辑学家哈斯凯尔·加里,他在数理逻辑方面上的工作使得函数式编程语言有了广泛的基础。在Haskell中,“函数是第一类对象。作为一门函数编程语言,主要控制结构是函数。Haskell语言是1990年在编程语言Miranda的基础上标准化的,并且以λ演算为基础发展而来。

特点

静态类型

Haskell 中的每个函数和表达式在编译时都有一个确定的类型。通过函数应用组合而成的所有类型必须都匹配。否则 Haskell 编译器或解释器会察觉出类型不正确的表达式,并拒绝这些表达式的执行。类型不仅仅是一种形式上的保证,而且是一种表达程序结构的语言。

示例:

Haskell 的值都有一个类型。

1
2
3
4
char = 'a' :: Char
num = 1 :: Int
addOne :: Int -> Int
addOne x = x + 1

:: 符号用来表示类型,比如 exp :: T 就表示 exp 的类型是 T。此处代码定义了变量 char 的值为 a ,并指定变量 char 的类型为 Char型。定义了变量 num 的值为 1 ,并指定变量 num 的类型为 Int 型。此处定义了函数 addOne 将传入的参数 x1 后返回,并指定了函数的类型为输入 Int 型参数、返回 Int 型参数。

你需要给函数传入正确类型的参数,否则编译器将拒绝执行程序。

如果我们给 addOne 函数传入字符 c 并执行 addOne 'c' 编译器将报错:

1
2
3
4
• Couldn't match expected type ‘Int’ with actual type ‘Char’
• In the first argument of ‘addOne’, namely ‘'c'’
In the second argument of ‘($)’, namely ‘addOne 'c'’
In a stmt of a 'do' block: print $ addOne 'c'

可以看到编译器指出了参数类型不正确的错误。

纯函数

Haskell 中的每个函数都是数学意义上的函数(也就是说,“纯”)。即使是有副作用的 IO 操作也只是描述了如何去做,由纯代码生成。没有任何的语句或指令,只有不可变变量的表达式(本地或全局),也不能像获取时间或随机数字那样获取状态。

下面的函数接收一个 Int 型参数,并返回一个 Int 型参数。通过类型声明可以知道这个函数不能产生任何副作用,它不能改变它的任何参数。

1
2
square :: Int -> Int
square x = x * x

square 函数接收了 Int 型的参数,并直接返回参数的平方,并没有对参数有其他的操作。

类型推导

在 Haskell 程序中不需要明确声明所有的类型。类型将通过双向统一每一种类型来推断。但是你也可以声明出每种类型或者让编译器为你编写便于使用的文档。

前面我们编写过 addOne 函数,如果我们不显式指明 addOne 的类型为 Int -> Int 那么编译器将如何处理呢。

这里我们编写一个 addTwo 函数,并在 ghci 中通过 :t 命令查看 addTwo 函数的类型。

1
2
addTwo x = x + 2
:t addTwo

输出:

1
addTwo :: Num a => a -> a

此处的 Num a => a 表示变量 a 的类型为 Num 类的。 Num 类在 Haskell 中是数字的基础类型,也就是说传入的参数是需要是数字类型的。为什么 Haskell 会这么推断呢?从函数的定义可以看到传入的参数与 2 相加,而在这里 Haskell 并不知道 2 是整数还是浮点数,因此将函数的参数类型推断为属于 Num 类的参数即可。

如果我们指定进行运算的数字为 Int 型,那么 Haskell 将能正确推断出函数接收 Int 型的参数。这里我们编写一个加 3 的函数。

1
2
addThree x = x + 3 :: Int
:t addThree

输出:

1
addThree :: Int -> Int

并发

由于 Haskell 显式地处理函数作用,Haskell 很适合并发编程。其旗舰编译器GHC不但具有高性能垃圾回收器还有包含许多有用的并发基元和抽象的轻量库。

由于 Haskell 都是不可变变量,在并发操作时也就不需要应用到锁等方法解决数据争用的问题,也不用担心死锁的问题,也就在并发操作上更具有优势。

惰性求值

函数不直接计算它们的参数。这意味这程序可以很好的进行组合,只需要编写普通函数即编写可控制结构(如 if/else)。Haskell 代码的纯度使得把函数融合在一起十分容易,从而带来性能优势。

在 Haskell 中可以通过 .. 操作符生成指定范围的 List,[1..100] 生成了包含 1100 的数组。也可以通过 repeat 函数生成一个包括该值的无限 List,repeat 5 生成了包含无限个 5 的 List。

Haskell 有一个 take 函数可以从 List 中取出指定个数的 List。take 函数的类型声明为: take :: Int -> [a] -> [a]

1
2
xs = repeat 5
take 8 xs

输出:

1
[5,5,5,5,5,5,5,5]

在定义了变量 xs 时,Haskell 并不会直接对 xs 求值,否则会没完没了。而是会等着,等到 take 函数的调用,当 take 函数说从 xs 中取出 8 个时,Haskell 才进行运算。

基本语法

注释

在行首使用 -- 为行注释,

使用 {--} 包裹为段落注释。

1
2
3
4
5
-- 行注释

{-
段落注释
-}

声明

通过使用 :: 手动声明表达式的类型。

1
3 :: Float -- 这里声明3为Float类型

函数

Haskell 中函数分为普通函数和中缀函数。普通函数调用优先级最高,高于任何中缀函数。

1
2
3
4
print "hello haskell" -- 普通函数

2 + 3 -- 中缀函数
(+) 2 3 -- 普通函数

使用圆括号可以把普通函数当做中缀函数调用。

if 语句

Haskell 中的 if 语句不同于其他语言的 if 语句,在 Haskell 中更像是三目运算符。Haskell 的 if 语句必须有 else 而且没有 else if。

1
2
3
4
5
isBig :: Int -> String
isBig x =
if x > 5
then "Big"
else "Small"

case of 语句

Haskell 中的 case of 语句很像其他语言中的 switch 语句,可以对多种情况进行判断处理。一个 case 表达式至少有一个选项,每个选项必须有至少一个主体。每个主体都有相同的类型,并且整个表达式的类型都为该类型。

1
2
3
4
5
6
typeOf :: Int -> String
typeOf x =
case x of
1 -> "Good"
2 -> "Not Bad"
3 -> "Bad"

参考资料: