Haskell 作为纯函数式语言,函数是“第一类公民”,其定义和调用机制是整个语言的核心。以下从规范、本质、风格、特殊形式、核心特性五个维度,全方位拆解函数的所有关键规则。
一、函数定义规范
1.1 核心构成:类型签名 + 函数体
Haskell 函数定义的完整形式包含「类型签名(可选但推荐)」和「函数体」,严格遵循“纯函数”原则(无副作用、输入决定输出)。
(1)类型签名规范
-
语法格式:
函数名 :: 输入类型1 -> 输入类型2 -> ... -> 返回类型->表示函数的“输入→输出”关系,右结合(核心,柯里化的基础);- 类型签名是“契约”,编译器会校验函数体是否符合签名;
- 类型变量(如
a/b)表示多态,需小写开头;具体类型(如Int/[a])大写开头。
-
示例:
-
-- 单参数函数:计算平方 square :: Int -> Int -- 输入Int,返回Int square x = x * x -- 多参数函数:两数相加(数值多态) add :: Num a => a -> a -> a -- 带类型类约束的多态 add x y = x + y -- 高阶函数:接收函数+列表,返回列表 applyTwice :: (a -> a) -> [a] -> [a] -- 输入:函数(a→a) + 列表[a],返回列表[a] applyTwice f xs = map f (map f xs)
-
(2)函数名/参数/返回值规则
-
函数名:
- 普通函数名:小写开头(如
square/add),可包含字母、数字、'、_; - 运算符(中缀函数):由特殊符号组成(如
+/*/<$>),可通过括号转为前缀调用(如(+))。
- 普通函数名:小写开头(如
-
参数:
- 无括号、无逗号分隔,空格分隔即可(如
add x y而非add(x,y)); - 参数是“模式”,支持模式匹配(如
sum [] = 0; sum (x:xs) = x + sum xs)。
- 无括号、无逗号分隔,空格分隔即可(如
-
返回值:
- 函数体最后一个表达式的值即为返回值,无
return关键字(return是 Monad 操作,非返回值); - 纯函数必须有返回值(无 void 函数,空值用
()表示)。
- 函数体最后一个表达式的值即为返回值,无
1.2 顶层函数 vs 局部函数
| 维度 | 顶层函数 | 局部函数 |
|---|---|---|
| 定义位置 | 模块顶层(不在任何函数/表达式内) | 函数体/do块/where/let 表达式内 |
| 作用域 | 整个模块(或导出到其他模块) | 仅定义所在的局部作用域 |
| 类型签名 | 推荐显式写(增强可读性) | 可省略(编译器自动推导) |
| 常见场景 | 模块对外暴露的核心功能 | 辅助逻辑、临时复用、闭包 |
示例:局部函数的两种写法(where / let)
-- 顶层函数:计算三角形周长(依赖局部函数)
trianglePerimeter :: Double -> Double -> Double -> Double
trianglePerimeter a b c = sideSum -- 最后一个表达式是返回值
where -- where 绑定:局部函数,作用域是顶层函数体
sideSum = a + b + c -- 局部变量(本质是无参函数)
validate = a > 0 && b > 0 && c > 0 -- 局部辅助函数
-- let 绑定:局部函数,表达式内生效
circleArea :: Double -> Double
circleArea r =
let pi' = 3.14159 -- 局部常量
square x = x * x -- 局部函数
in pi' * square r -- in 后是返回值
二、多参数函数的本质(柯里化)
Haskell 中没有真正的“多参数函数” ,所有多参数函数都是“返回函数的函数”,这是由 -> 的右结合特性决定的。
2.1 柯里化(Currying)核心原理
-
定义:将接收 N 个参数的函数,转换为 N 个嵌套的单参数函数。
-
语法体现:
a -> b -> c≡a -> (b -> c)(右结合)。 -
示例拆解:
-
-- 表面:两参数函数 add :: Int -> Int -> Int add x y = x + y -- 本质:单参数函数,返回另一个单参数函数 add :: Int -> (Int -> Int) add x = \y -> x + y -- 等价写法(λ表达式)
-
2.2 部分应用(Partial Application)
柯里化的核心价值:调用函数时可只传部分参数,返回“等待剩余参数的函数”。
示例:部分应用实战
-- 1. 部分应用 add,固定第一个参数为 5
add5 :: Int -> Int
add5 = add 5 -- 等价于 add5 y = 5 + y
-- 2. 部分应用列表映射函数,固定映射逻辑
incrementAll :: [Int] -> [Int]
incrementAll = map (+1) -- map 的第一个参数是 (Int→Int),返回 ([Int]→[Int])
-- 3. 多参数运算符的部分应用(sections 语法,下文详解)
isPositive :: Int -> Bool
isPositive = (> 0) -- 部分应用 >,固定右侧参数为 0
2.3 函数作为一等公民
Haskell 中函数和整数/字符串一样,可:
- 作为参数传递(高阶函数);
- 作为返回值返回(闭包);
- 绑定到变量;
- 存入数据结构(如列表、元组)。
示例:函数作为一等公民
-- 1. 函数作为参数(高阶函数)
apply :: (a -> b) -> a -> b
apply f x = f x
-- 2. 函数作为返回值(闭包:捕获外部变量)
makeAdder :: Int -> (Int -> Int)
makeAdder n = \x -> x + n -- 捕获 n,返回的函数可使用 n
-- 3. 函数存入列表
mathOps :: [Int -> Int -> Int]
mathOps = [(+), (-), (*)] -- 列表元素是函数
-- 4. 函数绑定到变量
multiply = (*) -- multiply 是函数变量
三、函数调用风格
3.1 前缀调用 vs 中缀调用
| 调用风格 | 语法格式 | 适用场景 | 示例 |
|---|---|---|---|
| 前缀调用 | 函数名 参数1 参数2 ... | 普通函数、多参数函数 | add 1 2、map (+1) [1,2,3] |
| 中缀调用 | 参数1 函数名 参数2 | 运算符、双参数函数 | 1 + 2、[1,2] ++ [3,4] |
关键规则:
- 普通函数可通过
`函数名`转为中缀调用; - 运算符可通过
(运算符)转为前缀调用。
示例:两种风格互转
-- 普通函数转中缀
1 `add` 2 -- 等价于 add 1 2 → 3
-- 运算符转前缀
(+) 1 2 -- 等价于 1 + 2 → 3
(++) [1] [2] -- 等价于 [1] ++ [2] → [1,2]
3.2 运算符优先级与结合性
Haskell 定义了运算符的优先级(0-9,越高越先执行)和结合性(左/右/无),避免歧义。
核心规则:
-
优先级:数字越大,优先级越高(如
*优先级 7,+优先级 6,故1 + 2 * 3 = 7); -
结合性:
- 左结合(如
+/*):a + b + c = (a + b) + c; - 右结合(如
->/:):a : b : c = a : (b : c); - 无结合(如
==/<):不能连续调用(需加括号,如(a == b) && (b == c))。
- 左结合(如
自定义运算符:
可通过 infixl/infixr/infix 定义自定义运算符的优先级和结合性:
-- 定义自定义运算符 +++:优先级 6,左结合
infixl 6 +++
(+++) :: [a] -> [a] -> [a]
xs +++ ys = xs ++ ys -- 等价于 ++
-- 调用:优先级与 + 相同,左结合
[1,2] +++ [3,4] +++ [5,6] -- 等价于 ([1,2] +++ [3,4]) +++ [5,6]
3.3 Sections
Sections 是运算符部分应用的简化语法,分为“左 section”和“右 section”,核心是固定运算符的一侧参数。
| Section 类型 | 语法 | 等价写法 | 示例 |
|---|---|---|---|
| 右 section | (运算符 常量) | \x -> x 运算符 常量 | (> 0) → \x -> x > 0 |
| 左 section | (常量 运算符) | \x -> 常量 运算符 x | (1 +) → \x -> 1 + x |
实战场景:
-- 1. 过滤正数(右 section)
filter (> 0) [-1, 2, -3, 4] -- → [2,4]
-- 2. 所有元素加 10(左 section)
map (10 +) [1,2,3] -- → [11,12,13]
-- 3. 列表拼接固定前缀(左 section)
map ("prefix: " ++) ["a", "b"] -- → ["prefix: a", "prefix: b"]
-- 注意:无参数的运算符(如 negation -)需特殊处理
map (-) [1,2,3] -- 错误:- 是单目运算符,需写成 map (0 -) [1,2,3] → [-1,-2,-3]
四、匿名函数(λ表达式)
匿名函数是“无需命名的临时函数”,核心用于:临时逻辑、高阶函数参数、局部简短逻辑。
4.1 基本语法
-
格式:
\参数1 参数2 ... -> 表达式- `` 是 λ 的简写(λ 在 Haskell 中需转义);
- 参数空格分隔,
->后是函数体(单个表达式); - 函数体可嵌套、可调用其他函数。
4.2 单/多参数匿名函数
-- 1. 单参数:计算平方
\x -> x * x -- 等价于 square x = x * x
-- 2. 多参数:两数相加
\x y -> x + y -- 等价于 add x y = x + y
-- 3. 高阶匿名函数:接收函数+值,调用两次
\f x -> f (f x) -- 等价于 applyTwice f x = f (f x)
4.3 嵌套匿名函数
匿名函数可嵌套,用于复杂逻辑的临时封装:
-- 嵌套匿名函数:先加 1,再平方
map (\x -> (\y -> y * y) (x + 1)) [1,2,3] -- → [4,9,16]
-- 简化写法(去掉内层命名)
map (\x -> (x + 1) * (x + 1)) [1,2,3]
4.4 实战场景(匿名函数的核心用途)
场景1:高阶函数的临时参数(最常用)
-- 排序:按元组第二个元素降序(临时逻辑,无需命名)
sortBy ((a,b) (c,d) -> compare d b) [(1,3), (2,1), (3,2)]
-- → [(1,3), (3,2), (2,1)]
-- 过滤:字符串长度大于 5
filter (\s -> length s > 5) ["a", "hello", "haskell"]
-- → ["haskell"]
场景2:部分应用的补充(无法用 sections 时)
-- 需求:列表元素乘以 2 再加 3(无法用 sections 简化)
map (\x -> x * 2 + 3) [1,2,3] -- → [5,7,9]
场景3:闭包(捕获外部变量)
-- 动态生成匿名函数:捕获外部变量 n
makeScaler :: Int -> (Int -> Int)
makeScaler n = \x -> x * n -- 捕获 n,返回匿名函数
scaleBy5 = makeScaler 5
scaleBy5 10 -- → 50
4.5 匿名函数的限制
- 函数体只能是单个表达式(不能有多行、多个语句);
- 复杂逻辑建议提取为命名函数(where/let),提升可读性。
五、函数赋值与传参(核心特性)
5.1 函数变量绑定
Haskell 中函数可绑定到变量,变量本质是“函数的别名”,无“赋值”概念(不可变)。
-- 1. 简单绑定:add 绑定到 (+)
add = (+) -- add 是 (+) 的别名,类型:Num a => a -> a -> a
-- 2. 部分应用绑定:add10 是 add 固定第一个参数的结果
add10 = add 10 -- 类型:Num a => a -> a
-- 3. 多态函数绑定
identity :: a -> a
identity = \x -> x -- 绑定匿名函数到 identity
5.2 函数作为参数(高阶函数)
这是函数式编程的核心,Haskell 内置大量高阶函数(map/filter/foldl/foldr)。
示例:自定义高阶函数
-- 1. 通用遍历函数:对列表每个元素应用函数
forEach :: (a -> ()) -> [a] -> ()
forEach f [] = ()
forEach f (x:xs) = f x >> forEach f xs -- IO 上下文示例
-- 调用:打印列表每个元素
forEach (\x -> putStrLn ("Element: " ++ show x)) [1,2,3]
-- 2. 函数组合器:将两个函数组合为一个(f . g = \x -> f (g x))
compose :: (b -> c) -> (a -> b) -> a -> c
compose f g = \x -> f (g x)
-- 调用:先平方,再加 1
add1AfterSquare = compose (+1) (\x -> x * x)
add1AfterSquare 3 -- → 10
5.3 函数作为返回值(闭包)
函数可返回另一个函数,且返回的函数可“捕获”外部作用域的变量(闭包)。
示例:闭包实战
-- 1. 计数器(纯函数版:无状态,每次返回新函数)
counter :: Int -> (Int, Int -> Int)
counter n = (n, \x -> counter (n + x) |> fst) -- 简化写法
-- 调用:
let (c1, inc) = counter 0
c1 -- → 0
let c2 = inc 1
c2 -- → 1
let c3 = inc 2
c3 -- → 3
-- 2. 配置化函数:返回定制化的验证函数
makeValidator :: String -> (String -> Bool)
makeValidator prefix = \s -> prefix `isPrefixOf` s -- 捕获 prefix
-- 调用:
validateUser = makeValidator "user_"
validateUser "user_123" -- → True
validateUser "admin_456" -- → False
5.4 函数传参的特殊规则
-
惰性求值:参数仅在需要时计算(非严格求值);
-
模式匹配传参:参数可直接写模式,编译器自动校验穷尽性;
-
-- 模式匹配传参:处理 Maybe 类型 handleMaybe :: Maybe a -> String handleMaybe Nothing = "Empty" handleMaybe (Just x) = "Value: " ++ show x
-
-
无副作用传参:参数是纯值,多次传相同参数结果一致(引用透明)。
总结(核心关键点)
- 函数定义:类型签名(推荐显式)+ 函数体,纯函数无副作用,参数支持模式匹配;
- 多参数本质:柯里化(嵌套单参数函数),部分应用是核心优势;
- 调用风格:前缀/中缀可互转,sections 简化运算符部分应用,优先级/结合性避免歧义;
- 匿名函数:λ表达式,用于临时逻辑,函数体仅单个表达式;
- 一等公民:函数可作为参数/返回值/变量,闭包可捕获外部变量,是高阶函数的基础。