Skip to content

Latest commit

 

History

History
237 lines (157 loc) · 4.69 KB

04 常见语法.md

File metadata and controls

237 lines (157 loc) · 4.69 KB

常见语法

本章介绍一些花里胡哨的写法和常用函数.

条件和卫兵模式

看一个阶乘函数:

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

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].