因为我是 Windows 系统,在这里下载一个 GHC for Win 后解压缩,将解压缩后的目录中的 bin 添加到环境变量的 Path 中,在命令行中输入 ghci
就可用交互式的了。
其中 bin 目录下有一个 runhaskell.exe 文件,我们对一个后缀为 .hs 的文件应用这个可执行文件后,就能写下面的代码了,而不是在 ghci
中交互,虽然那样学习更方便。
值得一提的是除法,我们可以写出如下 main
函数并打印结果,如
main = do print (5 / 2)
结果为 2.5
而不是 Cpp 中的 2
,如果我们需要欧几里得除法则可以这样写
main = do print (5 `div` 2) -- div 5 2 也是一样的
结果为 2
而取模也不是通常的 %
符号而是
main = do print (5 `mod` 2) -- mod 5 2 也是一样的
用 True
和 False
表示,用 not
表示取反, &&
表示逻辑与, ||
表示逻辑或,如
main = do
let c = 10
if not (c > 11)
then putStr "c <= 10"
else putStr "c > 11"
需要注意括号的位置,基本上我们可以认为 not
是一个函数。
比较两个数
main = do
let c = 10
if 5 /= c
then putStr "c not eqaul to 5"
else putStr "c is 5"
注意这里的不等于使用的是 a /= b
而不是 a != b
,而等于则为 a == b
与其他语言是类似的。
main = do print (succ 8) -- 9
main = do print (min 10 11) -- 10
main = do print (max 10 11) -- 11
函数的一些顺序有下面这个例子
main = do print (succ 9 + max 5 4 + 1)
这等价于
main = do print ((succ 9) + (max 5 4) + 1)
doubleMe x = x + x
main = do
print (doubleMe 9) -- 18
squareMe x = x * x
main = do
print (squareMe 9) -- 81
当然也可以用小数调用这些函数如
doubleMe x = x + x
main = do
print (doubleMe 9.1) -- 18.2
也就是说我们写的函数实际上是一个泛型的函数!这很棒,对于限制类型后面会提到。
多参数的函数
addxy x y = x + y
main = do
print (addxy 7 8) -- 15
带有 if
的函数
plusOne x = if x == 0 then x + 1 else -1
main = do
print (plusOne 1)
print (plusOne 0)
当然我们可以调整一些缩进写成
plusOne x =
if x == 0
then x + 1
else -1
main = do
print (plusOne 1)
print (plusOne 0)
这样使得代码更具有可读性。
我不清楚是否应该称呼其为数组,我们可以写出这样的代码
main = do
let c = [1,2,3,4]
print c -- [1,2,3,4]
而我们常用的字符串如 "abc"
实际上为 ['a','b','c']
的语法糖,这与在 C 语言中有点类似,也就是说
main = do
print ("abc" == ['a','b','c']) -- True
是毫无疑问的。
对于 list 拼接也显得很自然,有
c = [1,2,3] ++ [2,3,4]
main = do
print c -- [1,2,3,2,3,4]
也有这样的拼接方法如
a = 10
b = [1,2,3]
main = do
print (a : b) -- [10,1,2,3]
下标访问,在 C 语言中我们常常会这样写
#include
int main(void) {
int a[10];
printf("%d", a[1]); // 未初始化,值可能为随机
return 0;
}
而在 Haskell 中则是
a = "abcdefg"
main = do
putChar (a !! 2) -- c
我们发现其索引开始也是 0
,这与一般的习惯很符合。
值得注意的是, list 之间也可以通过 <
、 >
、 >=
、 <=
比较,结果好像为字典序(但一般很少用到)。
head
函数只取首个元素
c = [1,2,3,4,5]
main = do
print (head c) -- 1
tail
函数为除了首个元素的其余元素的 list
c = [1,2,3,4,5]
main = do
print (tail c) -- [2,3,4,5]
last
函数为最后一个元素
c = [1,2,3,4,5]
main = do
print (last c) -- 5
init
函数为除了最后一个元素的其余元素的 list
c = [1,2,3,4,5]
main = do
print (last c) -- [1,2,3,4]
注意, head
不能应用于空的 list ,其余函数我没有测试。
length
函数返回 list 的长度
main = do
print (length [1,2,3]) -- 3
print (length []) -- 0
null
函数返回一个布尔值表示 list 是否为空
main = do
print (null []) -- True
print (null [1]) -- False
reverse
函数返回一个翻转的 list
c = [1,2,3]
main = do
print (reverse c) -- [3,2,1]
上面所说的函数全都是一个参数的函数,且参数都为 list ,下面的函数为两个参数,具体看示例为
take
函数表示截取前几个元素组成新的 list
c = [1,2,3,4,5]
main = do
print (take 3 c) -- [1,2,3]
print (take 0 c) -- []
print (take 8 c) -- [1,2,3,4,5]
drop
函数表示丢弃前几个元素组成新的 list
c = [1,2,3,4,5]
main = do
print (drop 3 c) -- [4,5]
print (drop 0 c) -- [1,2,3,4,5]
print (drop 100 c) -- []
maximum
函数与 minimum
函数想必不必多说
c = [1,2,3,4,5]
main = do
print (maximum c) -- 5
print (minimum c) -- 1
sum
函数返回和
c = [1,2,3]
main = do
print (sum c) -- 6
product
函数返回乘积
c = [1,2,3]
main = do
print (product c) -- 6
elem
函数告诉我们这个元素是否在 list 中
c = [1,2,3]
main = do
print (1 `elem` c) -- True
print (-1 `elem` c) -- False
通常用一个中缀表达式来写,当然也可以写作 elem 1 c
,因为更自然,与之前的 div
和 mod
是类似的。
范围的 list 可以这样写
main = do
print [1..5] -- [1,2,3,4,5]
print [2,4..10] -- [2,4,6,8,10]
print [3,6..20] -- [3,6,9,12,15,18]
但是不能写 [5..1]
而必须写 [5,4..1]
,我们可以理解为等差数列前两项以及最后一项,但小数的时候不同
main = do
print [0.1,0.3 .. 1] -- [0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
cycle
函数可以将一个东西无限制的拼接如
main = do
print (take 10 (cycle [1,2,3])) -- [1,2,3,1,2,3,1,2,3,1]
于是我们只能用 take
函数将其取出部分。
repeat
函数则有
main = do
print (take 10 (repeat 5)) -- [5,5,5,5,5,5,5,5,5,5]
也有更方便的函数 replicate
可以做到
main = do
print (replicate 3 10) -- [10,10,10]
接下来是一个很厉害的工具,可以制作我们想要的 list 形态,例如我想要 { x ∣ 0 ≤ x ≤ 20 , x m o d 3 = 1 , x ∈ Z } \{x\mid 0\leq x\leq 20,x\bmod 3=1,x\in\mathbb{Z}\} {x∣0≤x≤20,xmod3=1,x∈Z} 的 list 可以写
main = do
print [x | x <- [0..20], x `mod` 3 == 1]
这甚至与数学中很类似。
我们也可以写函数接收一个 list 返回出想要的 list 如
weWant xs = [x | x <- xs, odd x]
main = do
print (weWant [0,1,7,9]) -- [1,7,9]
上面用到了函数 odd
同样的也有 even
。
不止如此,我们也可以写出
prodEachElem xs ys = [x * y | x <- xs, y <- ys]
main = do
print (prodEachElem [1,2,3] [4,5,6]) -- [4,10,18]
用数学表达则为 x = { 1 , 2 , 3 } , y = { 4 , 5 , 6 } , z = { a b ∣ a ∈ x , b ∈ y } x=\{1,2,3\},y=\{4,5,6\},z=\{ab\mid a\in x,b\in y\} x={1,2,3},y={4,5,6},z={ab∣a∈x,b∈y} 很自然。
回顾之前的函数 length
我们可以用 sum
写一个自己的版本为
length' x = sum [1 | _ <- x]
main = do
print (length' []) -- 0
print (length' [1,2,3]) -- 3
他可以正常的工作,其中下划线 _
代表我们并不关心这个是什么。
中文一般为元组。元组一般为 (a,b)
这样的形式。
fst
函数返回元组中的首个元素,而 snd
返回第二个。
main = do
print (fst (1,"WWW")) -- 1
print (snd (2,"WWWA")) -- "WWWA"
有一个很酷的函数为 zip
可以制作元组如
main = do
print (zip [1,2,3] ["A","B","C"]) -- [(1,"A"),(2,"B"),(3,"C")]
print (zip [1..3] ['A','B','C']) -- [(1,'A'),(2,'B'),(3,'C')]
print (zip [1..] ['A','B']) -- [(1,'A'),(2,'B')]
可以看到是以 length
较短的标准制作的,这样的话写出 [1..]
这样看似无穷多的 list 也变得合理了(惰性?)。
当然,元组也远远不止两个元素,也可以写出
d = [(a,b,c) | a <- [1,3..10], b <- [2,4..7], c <- [7,11..18]]
main = do
print d
注意这里并不是 zip
函数,所以不要写无穷无尽的,我们可以将这里视为三重的循环体。刚刚出了事故我的电脑死机了,看来这个编译器会一丝不苟的执行下去呢,并没有检测是否会造成死循环。