本章介绍一些花里胡哨的写法和常用函数.
看一个阶乘函数:
factorial1 :: Int -> Int
factorial1 0 = 1
factorial1 n = n * (factorial1 (n - 1))
可以写成条件:
factorial2 :: Int -> Int
factorial2 n =
if n == 0 then
1
else
n * factorial2 (n - 1)
又可以写成:
factorial3 :: Int -> Int
factorial3 n
| n == 0 = 1
| otherwise = n * (factorial3 (n - 1))
这种写法称为卫兵模式
, 是if-else
的语法糖.
对于一个传入函数的函数:
ff :: (Int -> Int) -> Int
ff f = f 1
可以定义函数, 然后调用它:
f1 :: Int -> Int
f1 a = a + 1
x1 :: Int
x1 = ff f1
但有些时候, 函数只用一次, 单独写出来就很麻烦, 于是可以写匿名函数.
匿名函数的形式是:
(\参数 ... -> 表达式)
例如:
x2 :: Int
x2 = ff (\a -> a + 1)
有函数:
showPerson :: { first :: String, last :: String } -> String
showPerson { first: x, last: y } = y <> ", " <> x
这要输入一个记录类型, 记录里的key是first和last, 如果输入的值没有这两个key当然会报错.
但是, 如果输入的值有这两个key, 同时还有多余的key呢?
照理来说, 只要保证有这两个key就足够了, 有多余的不用就好了.
不幸的是, 记录类型是一个整体, key不同就意味着类型不同, 所以有多余key的值也是传不进来的.
但可以使用一个特殊的语法:
showPerson :: forall r. { first :: String, last :: String | r } -> String
showPerson { first: x, last: y } = y <> ", " <> x
声明一个泛型r, 然后在记录类型中写竖线r, 这说明记录类型中的剩余key会被匹配到泛型r上.
这样就可以传入有多余key的值了, 这种写法称为记录行多态.
x3 :: String
x3 = showPerson { first: "aaa", last: "bbb" }
x4 :: String
x4 = showPerson { first: "aaa", last: "bbb", sex: "ccc" }
对于函数
showPerson1 :: { first :: String, last :: String } -> String
showPerson1 { first: first, last: last} = last <> ", " <> first
函数的实现使用了模式匹配, 将key为first的值匹配成了first, last亦然.
对于key和value一致的情况, 可以省略, 写成:
showPerson1 :: { first :: String, last :: String } -> String
showPerson1 { first, last } = last <> ", " <> first
同理, 构造值的时候也可以:
unknownPerson :: String -> String -> { first :: String, last :: String }
unknownPerson first last = { first, last }
有时候需要既匹配模式, 又获得参数的整体, 例如:
sortPair :: Array Int -> Array Int
sortPair arr@[ x, y ]
| x <= y = arr
| otherwise = [ y, x ]
sortPair _ = []
这里, 指定了模式[x,y]
, 同时在前面加上了arr@
.
这样写, 既可以使用x
,y
来访问匹配值, 又可以用arr
访问原值.
newtype是更高效的data, 没有封装, 比data更快.
但newtype有限制, 即newtype的构造子只能有一个:
newtype A1 = G1 Int
折叠是限制更严格的循环.
折叠的形式是:
折叠函数 初始值 数组
一个例子:
arrSum :: Array Int -> Int
arrSum arr = foldl (\s a -> s + a) 0 arr
运行流程是:
先将初始值作为s, 然后取数组的第0个元素作为a, 调用折叠函数.
得到的结果作为新的s, 取数组的下一个值作为a, 调用循环函数.
直到数组取完, 返回最后的s.
这样调用:
x7 :: Int
x7 = arrSum [ 1, 2, 3 ]
结果会是6.
可以使用一个函数, 将一个函数分别应用在数组的所有元素上, 得到的结果组成新的元素.
x8 :: Array Int
x8 = map (\a -> a + 1) [ 1, 2, 3 ]
这会得到[2,3,4]
.
可以提供一个函数, 将数组里所有元素传入此函数, 当函数返回真时元素保留, 否则过滤掉元素.
x9 :: Array Int
x9 = filter (\n -> n >= 2) [ 1, 2, 3 ]
这将保留数组中大于等于2的元素, 结果是[2,3]
.
可以通过(..)
操作快速生成等差数组:
x10 :: Array Int
x10 = 1 .. 5
这会得到[1,2,3,4,5]
.
可以将高维数组拉平:
x11 :: Array (Array Int)
x11 = [ [ 1, 2, 3 ], [ 4, 5 ], [ 6 ] ]
x12 :: Array Int
x12 = concat x11
这会得到[1,2,3,4,5,6]
.