haskell函数进阶

Haskell 函数进阶

本文会涉及 Haskell 中的部分进阶函数概念.这些功能是 Haskell 编程的基础,也是许多高级功能的核心.函数不仅是输入和输出的工具,它们在 Haskell 中是一等公民,可以像数据一样传递、组合和操作.这种特性使得 Haskell 编程语言能够通过组合简单的函数来构建复杂的程序结构.Haskell 还具有惰性求值、类型推导和强大的高阶函数功能,让它非常适合表达数学模型和处理抽象问题.由于haskell官方库的东西太多太杂,而且有很多完全是实验性质的内容,本文只涉及一些极为常用的部分.

简化函数

简化函数是 Curry 化的应用,最常见的用途就是省去这个函数紧接着的参数

示例

1
2
3
4
5
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)

isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])

说明

  • divideByTen: 这个函数接受一个数字,将其除以 10.它实际上是通过 Currying 技术将除法操作简化成了一个只接受一个参数的函数.
  • isUpperAlphanum: 检查字符是否属于大写英文字母.

注意:

  • (-4) 表示负号操作.
  • subtract 4 表示减去 4 的操作.


函数作为参数

Haskell 中的函数可以作为输入参数传递.

类型 (a -> a):表示 f 是一个将 a 类型值映射到 a 类型值的函数.

示例

1
2
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

说明

  • applyTwice:这个函数接受一个函数 f 和一个值 x,然后将 f 应用两次于 x.例如,applyTwice (+3) 10 会先将 10 增加 3 得到 13,再把 13 增加 3 得到 16.

更多示例

1
2
3
4
5
6
7
8
9
10
11
12
applyTill :: (Ord t1, Num t1) => (t2 -> t2) -> t2 -> t1 -> t2
applyTill f x n
| n > 1 = f (applyTill f x (n - 1))
| otherwise = f x

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

flip' :: (a -> b -> c) -> b -> a -> c
flip' f y x = f x y

  • applyTill: 这个函数会将函数 f 应用到 xn 次,直到达到终止条件.例如,applyTill (+1) 0 5 会递归地把 +1 操作应用 5 次.
  • zipWith': 按照给定的二元函数,将两个列表对应位置的元素进行组合.
  • flip': 交换二元函数的参数顺序.


Map and Filter

map 和 filter 是列表操作的两大经典函数,它们的核心思想是将函数作用于列表中的每个元素,或者根据条件筛选元素.map 是一种批量操作,它将给定的函数作用于列表中的每个元素.而 filter 则是基于条件对列表进行筛选.它们都是典型的高阶函数,因为它们接受其他函数作为参数.这种函数式操作使得代码更加简洁,避免了显式的循环结构.

示例

1
2
3
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

  • map:将给定的函数 f 应用于列表的每个元素.例如,map (*2) [1,2,3] 会返回 [2,4,6].
1
2
3
4
5
filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
  • filter:根据谓词 p过滤列表中的元素.例如,filter (>2) [1,2,3,4] 会返回 [3,4],筛选出大于 2 的元素.


Fold

折叠操作是一种将列表中的元素合并成一个单一结果的方法.Haskell 提供了 foldl 和 foldr 两种方式,分别从左到右和从右到左地遍历列表.它们本质上是一种将递归模式转化为迭代的方式,在处理列表时非常高效.

示例

1
2
sum' :: (Num a) => [a] -> a
sum' xs = foldl (CHUacc x -> acc + x) 0 xs

  • sum’ :使用 foldl 从左到右对列表求和.foldl 的第一个参数是一个累积函数,0 是初始值,xs 是要遍历的列表.
1
2
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr (CHUx acc -> f x : acc) [] xs
  • map’ :使用 foldr 实现的 map,它将每个元素通过函数 f 转换后返回新的列表.

  • 折叠的关键:遍历列表并根据元素返回结果.

  • 区别:foldl 从左到右,foldr 从右到左,右折叠可以处理无限列表.


运算符 $ 和 组合 .

运算符 $

$ 运算符是 Haskell 中一个非常特殊的运算符,它的优先级非常低(低于所有常见的操作符).事实上, $ 运算符的优先级是最低的(0),所以它会吞噬掉表达式中多余的括号.$ 运算符的作用是将它右边的表达式应用到左边的函数上,等价于将一个函数应用于一个表达式.

在实际使用中,$ 最主要的作用是用来 减少括号,特别是在嵌套的函数调用中,避免过多的括号.

示例

1
2
ghci> sqrt $ 3 + 4 + 9
ghci> map ($ 3) [(4+), (10*), (^2), sqrt]

  • $ :使用 $ 运算符,表达式 3 + 4 + 9 会先被求值,然后传递给 sqrt 函数,等同于 sqrt (3 + 4 + 9).

组合.

. 运算符是 函数组合运算符,它的作用是将多个函数组合成一个函数.. 运算符的优先级是较高的(9),比大多数运算符都要高.它是右结合的,也就是说它会将最右边的函数优先组合..通过这个运算符,您可以把多个简单的函数“串联”起来,形成一个新的函数,从而避免冗长的函数调用.

组合运算符:

1
fn = ceiling . negate . tan . cos . max 50

  • . :将多个函数组合起来,使得函数调用更直观.fn 是一个组合函数,它先求 x 和 50 的最大值,再进行 cos 和 tan 运算,最后取负并取整.

$ 和 . 结合使用的例子

有时,我们会在同一个表达式中同时使用 $ 和 .,这通常是为了优化代码的结构,使其更加简洁.例如:

1
2
ghci> ceiling . sqrt $ 3 + 4 + 9
4

在这个例子中,$ 用来减少括号的使用,ceiling . sqrt 则先计算平方根,然后对结果取整.$ 确保 3 + 4 + 9 会先被求值,然后传递给组合后的函数.