haskell基本语法

语法入门

haskell作为一个实验性强于实践性的语言,有很多基础语法与传统的c系差异巨大,以往的知识,除了算法,难以直接代入,基本上就是要完全重新开始学.

本文主要面向对c,cpp等命令式编程语言熟悉的读者,主要的内容在于一些思维陷阱和新颖语法的记录,对于haskell中非常基础并且和其他语言相似的部分不会着墨.

关于教材与参考:

不要在入门的时候再看什么中文教材了,尤其是《Haskell函数式编程入门》这种教材.它具有典型的缺陷,比如一次性引入过多新知识而没有完整解释,包含大量与程序无关的冗长内容,对某些可能已经更改的语法细节过度纠结,缺乏渐进的示例,语言表述不准确,甚至有些内容显得玄妙或晦涩.这种教材更适合作为字典使用,而不是入门教材.

推荐 learn you a haskell.这个教材简明易懂,作为入门来说非常适合,英语也比较平白无华,属于说人话而不怎么说废话的教材.更好的是在线版本免费

haskell可以下载安装后使用vscode作为ide, 常见解释器ghci 即 glasgow haskell compiler iterative, 便于入门学习


基本常识

不等于用/=来表示

Haskell中用空格来表示参数直接的间隔,不需要,
比如

1
add 1 2

如果你看到类似 bar (bar 3) 的表达式,它并不是将 bar3 作为参数传递给 bar,而是先用 3 调用函数 bar,得到一个结果,然后再用这个结果调用一次 bar.在C语言中,类似于 bar(bar(3)).

Haskell中的函数定义不依赖于文本顺序.这个意味着在程序文本中随意调换不同函数的顺序不会有什么影响.

函数名称以小写字母开头,类型名称以大写字母开头.这个并不只是约定俗成的传统,而是系统强制的要求

haskell中没有常见意义里的循环遍历,而是采用函数的方式处理,基础语法中暂不涉及,放在后面函数部分中


if-else

具体格式为

1
if condition then action1 else action2

else 是强制要求的.


列表

在haskell中数组的本质:语法糖,对列表的简写,下面的写法是完全等价的
$[a_1,a_i,a_n] == a_1:a_i:a_n:[] == a_1:a_i:a_n$
其中x:xs代表元素x连接列表xs,[]代表空集

由于列表本质是同类元素连接的属性,一个列表中绝对只能含有完全同类的元素

字符串也是一个语法糖,其完全等价于字符数组
"Ciallo" == ['C','i','a','l','l','o'] == 'C':'i':'a':'l':'l':'o':[]

集合中的空集,注意:[][[]][[],[],[]] 是完全不同的.第一个是空列表,第二个是包含一个空列表的列表,第三个是包含三个空列表的列表.

数组下标从0开始,下标取值:!!

1
2
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1
33.2

++ 用于合并数组,两个操作数必须都是数组.

1
2
ghci> "114" ++ "514"
"114514"

比较大小:同类型按字典序比较.

headtail 表示列表的头部和尾部,initlast 表示列表的初始部分和最后一个元素.

take 从列表前面取若干元素,drop 从列表中删除前若干元素.

head _______________________tail
|[-----------------------------]
********************************
[-----------------------------]|
init________________________last

elem 判断元素是否存在于列表中.

生成数列示例:

1
ghci> take 24 [13,26..]

循环:

1
2
3
4
5
ghci> take 10 (cycle [1,2,3])
[1,2,3,1,2,3,1,2,3,1]

ghci> take 12 (cycle "LOL ")
"LOL LOL LOL "

重复:

1
2
ghci> take 10 (repeat 5)
[5,5,5,5,5,5,5,5,5,5]

管道符号前面的部分称为输出函数,x 是变量,N 是输入集合,x <= 10 是谓词.这意味着集合包含了所有满足谓词的自然数的两倍值.

<- 表示属于:

1
2
3
4
5
6
7
8
ghci> [x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

ghci> [x*2 | x <- [1..10], x*2 >= 12]
[12,14,16,18,20]

ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]

组合名词和形容词:

1
2
3
4
ghci> let nouns = ["hobo","frog","pope"]
ghci> let adjectives = ["lazy","grouchy","scheming"]
ghci> [adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
["lazy hobo","lazy frog","lazy pope","grouchy hobo","grouchy frog","grouchy pope","scheming hobo","scheming frog","scheming pope"]

_ 表示任意值,仅关心其存在:

1
length' xs = sum [1 | _ <- xs]

与数论中对1求和相似.

过滤偶数示例:

1
2
3
ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

首先从 xxs 中取出每个子列表 xs,然后再从 xs 中取出符合条件的元素.


元组

元组,tuple,可以包含列表,例如字符串.

元组更加严格,因为每种不同大小的元组都有自己的类型,所以不能编写通用函数来向元组追加元素.

没有一元元组,或者说一元元组就是单个元素本身.

fstsnd 只能用于二元组.

zip 自动截取:

1
2
ghci> zip [5..15] ["im","a","turtle"]
[(5,"im"),(6,"a"),(7,"turtle")]

求满足条件的直角三角形:

1
2
3
ghci> let rightTriangles' = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2, a+b+c == 24]
ghci> rightTriangles'
[(6,8,10)]