函数#
函数定义#
<functionName> :: <paramType> -> ... -> <returnType>
参数类型:
::
之后定义函数的参数类型,每个参数都必须定义类型;多个参数类型用
->
顺序链接,->
默认为右结合;函数:当函数作为参数传入时,该函数参数类型声明格式不变,但整体用小括号
()
包围;-- all :: Foldable t => (a -> Bool) -> t a -> Bool -- ^~~~~~~~~~^: 函数作参数 -- odd :: Integral a => a -> Bool res2 = all odd [1,2,3,4] -- False
返回值类型:最后一个类型用于定义函数返回值的类型;
factorial :: Integer -> Integer
-- 该函数名为 'factorial',接受一个 Integer 类型 作为参数,
-- 返回一个 Integer 类型
函数应用#
<functionName> <parameterList>
应用:并列函数和参数列表,用空格分隔;
factorial 4 -- 求值为 4!
优先级:函数应用的优先级高于中缀运算符;
func 3 n+1 7 -- 同 (func 3 n)+(1 7) func 3 (n+1) 7
模式匹配#
<functionName> :: <parameterType> -> ... -> <returnType>
<functionName> <pattern> = <functionBody>
...
模式:
函数参数的可能形式;
模式可以是任何数据类型的字面量或标识符;
字面量表示其本身,而标识符匹配任何数据类型;
hailstone 1 = 1 -- 匹配数字 1 hailstone n = n + 1 -- 匹配任意值
模式中可以使用多个变量或字面量指定接受多个参数;
foo x y z = x + y + z -- 接受三个参数
若函数体中不使用参数,则多余的参数可用
_
代替;first :: (a, b, c) -> a first (x, _, _) = x -- 匹配数对中的第一个元素
语法格式:
第一行为函数类型声明,声明函数参数与返回值的类型;
类型声明之下为模式匹配,每行以函数名开始,表示所有模式都属于该函数,各函数名之间可空行,但必须为同一函数名,否则认为函数定义结束;
每行函数名后跟模式,表示函数参数的可能形态;
用
=
分隔模式和函数体;=
后跟函数体;模式数量不限;
当接受两个参数时,函数名部分可以使用反引号包围转换为中缀运算符;
myAdd :: (Num a) => a -> a -> a a `myAdd` b = a + b -- 等价于 myAdd a b = a + b
工作流程:
检查参数值是否符合参数类型,不符合则报错(
expected type
),符合则继续;在 Haskell 中,函数体自上向下逐一检查参数是否与模式相匹配;
- 若参数不匹配模式,则继续向下匹配;
- 若参数匹配模式,则解析模式对应的函数体;
若有匹配结果,则解析函数体,检查返回值是否符合返回值类型,不符合则报错(
expected type
),符合则结束函数;若无匹配结果,则报错(
non-exhaustive patterns
);备注
为确保模式匹配的全面性,建议在最后将标识符作为模式,以匹配任何参数。
1factorial :: Integer -> Integer -- 函数定义
2factorial 1 = 1 -- 第一个模式匹配,匹配整数 1
3factorial x = x * factorial (x - 1)
4 -- 第二个模式匹配,标识符 'x' 匹配任意值
5
6exp1 = factorial 1
7-- 匹配第一个模式,求值为 1
8-- exp1 == 1
9exp2 = factorial 3
10-- 不匹配第一个模式,但匹配标识符 'x',函数求值为 3 * factorial (3 - 1)
11-- exp2 == 6
12exp3 = factorial 0
13-- 匹配第二个模式 'x',求值为 0 * factorial (0 - 1)
14-- 死循环
守卫#
<functionName> <pattern>
| <condition> = <functionBody>
...
| otherwise = <functionBody>
守卫:用于检查表达式的真值;
语法格式:
守卫语法始于模式之后;
|
表示守卫的开始,通常缩进两个空格;|
后接条件,用于判断真值;=
后定义函数体;守卫可以有多个,多个守卫之间可以有空行,但必须在同一模式下,否则认为守卫定义结束;
otherwise
关键字:布尔值True
的别名,用于兜底;守卫也可以书写在同一行,但为了可读性,不建议这样书写;
max :: (Ord a) => a -> a -> a max a b | a > b = a | otherwise = b
工作流程:
- 参数类型检查;
- 匹配模式,若匹配成功则进入守卫,否则继续向下匹配;
- 从上至下依次检查表达式的真值,若为真,则解析函数体;
- 若为假,则继续向下检查;
- 若均为假,则继续匹配下一个模式;
- 后同模式匹配;
1foo :: Integer -> Integer
2foo 0 = 16
3foo 1 | "Haskell" > "C++" = 3
4 | otherwise = 4
5foo n | n < 0 = 0
6 | n `mod` 17 == 2 = -43
7 | otherwise = n + 3
8
9exp1 = foo 0 -- 匹配第一个模式,求值为 16
10exp2 = foo 36
11 -- 匹配最后一个模式,对守卫进行求值:
12 -- 36 < 0 判断为假,继续
13 -- 36 `mod` 17 == 2 判断为真,最终求值为 -43
14exp3 = foo 38
15 -- 匹配最后一个模式,对守卫进行求值:
16 -- 38 < 0 判断为假,继续
17 -- 38 `mod` 17 == 2 判断为假,继续
18 -- 最终求值为 38 + 3
绑定#
where <pattern> = <expression>
...
where
语句:将表达式绑定至指定名字,执行时,名字会替换为相应表达式;where
语句用于提高代码的重用率,避免相同代码重复出现;语法格式:
where
语句可定义多个绑定,一个where
关键字可引领多个绑定;=
前定义被绑定的名字,=
后定义绑定并要替换的表达式;- 一行书写一个绑定;
作用域:
当
where
语句位于函数之后时,作用域为当前函数;bmiTell :: (RealFloat a) => a -> a -> String bmiTell weight height | bmi <= skinny = "You're underweight." | bmi <= normal = "You're supposedly normal." | bmi <= fat = "You're overweight." | otherwise = "You're a whale!" where bmi = weight / height ^ 2 (skinny, normal, fat) = (18.5, 25.0, 30.0) -- 求值时,'bmi','skinny' 和其他标识符会替换为 'where' 语句中 '=' 后的部分
当
where
语句位于所有代码之前时,作用域为全局;
替换:
模式匹配:和函数模式匹配一样,
where
语句也支持模式匹配;1describeList :: [a] -> String 2describeList xs = "The list is " ++ what xs 3 where 4 what [] = "empty." 5 what [x] = "a singleton list." 6 what xs = "a longer list." 7 8res = describeList [2] -- "The list is a singleton list."
函数:
where
语句中可以定义并替换函数;calcBmis :: (RealFloat a) => [(a, a)] -> [a] calcBmis xs = [ bmi w h | (w, h) <- xs ] where bmi weight height = weight / height ^ 2