Haskell - 一些高级函数

Haskell - 一些高级函数

在haskell中的函数可以将一个函数作为参数传入,也可以返回一个参数。这样的玩法就非常丰富了。

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

如何使用它

1
2
3
4
main = do
print $ applyTwice (+3) 10
print $ applyTwice (*2) 10
print $ applyTwice (++ "lol") "hello"

输出

1
2
3
> 16
> 40
> "hellolollol"

定义了一个名为applyTwice的函数,它接受两个参数:一个是要应用两次的函数f,另一个是要应用f的参数xapplyTwice函数的返回值是f applied to f(x),即f applied twice to x

这个函数的实现非常简单,它的表达式式f (f x)表示先对x应用f,然后对结果再应用f。这个表达式的意思是:applyTwice f x等于f applied to f(x)

因为这里使用的类型和返回类型都是a, 所以不用在意到底传的是什么类型,也就是什么类型都可以传入进去。比如传入 (+3) 10,首先是括号内部,执行了 10 + 3的操作,得到一个一元函数后再对括号外面的f再进行操作一次,最终得到了结果是16。
函数签名也可以理解,先做括号里的a, 这里标明了第一个参数是第一个参数与返回值都是a的函数,第二个参数与返回值的类型也都是a。

实现zipWith

为了帮助理解高阶函数,书上给了一个例子,复刻标准库中的zipWith函数。他取一个函数和两个列表作为参数,然后使用两个列表中对应的元素去调用该函数。

1
2
3
4
5
6
7
8
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y: zipWith' f xs ys

main = do
print $ zipWith' (+) [1,2,3] [4,5,6]
print $ zipWith' (*) [1,2,3] [4,5,6]

其中,abc是表示输入数据类型的参数,[a][b]是表示输入数据的列表,[c]是表示输出数据的列表。

zipWith'函数的实现依赖于一个名为_的匿名函数,它表示一个忽略输入参数的函数。这个匿名函数被传递给zipWith'函数,用于处理输入数据。

zipWith'函数的实现首先检查两个输入列表是否都为空,如果是,则返回一个空列表。接下来,它检查第一个输入列表是否为空,如果是,则返回第二个输入列表的元素组成的列表。然后,它使用给定的函数处理第一个输入列表的第一个元素和第二个输入列表的第一个元素,并将结果添加到输出列表中。最后,它递归地调用zipWith'函数,并将输出列表传递给下一个递归调用。

zipWith'函数的参数分为两部分:

  1. 函数参数:这个参数是一个函数,它定义了如何将两个输入数据(如列表中的元素)组合成输出数据。这个函数的参数有两个,分别是ab,它们表示输入数据和输出数据的类型。在zipWith'函数中,这个函数被传递给_匿名函数,用于处理输入数据。
  2. 列表参数:这个参数是一个列表,它包含了两个子列表,分别表示输入数据和输出数据。在zipWith'函数中,这两个子列表分别是[a][b],它们表示输入数据的列表和输出数据的列表。在zipWith'函数的实现中,这两个子列表被传递给_匿名函数,用于处理输入数据。

zipWith'函数的签名中的三个参数分别表示输入数据的类型、输入数据的列表和输出数据的列表。在zipWith'函数的实现中,这三个参数被传递给_匿名函数,用于处理输入数据。

重新来看函数签名zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
函数接收了三个参数 ( a -> b -> c )[a][b] 结果返回[c]
前面两个函数没有什么多说的必要,就是如果有一个列表是空的就返回一个空列表,事情到了第三个列表就有意思了起来,f 匹配的是(a -> b -> c)(x:xs)取了 [a](y:ys)[b]。然后再做了一个递归运算,将列表的每一个元素拿出来,进行函数操作,十分的巧妙。

实现 flip

取一个函数作为参数,返回一个效果相同的新参数,两个函数的唯一区别是,新函数的前两个参数的顺序和原来的函数前个参数的顺序正好颠倒。

1
2
3
flip' :: (a -> b -> c) -> (b -> a -> c)
flip' f = g
where g x y = f y x

这个函数 flip' 接受一个二元函数 f 作为参数,并返回一个新的二元函数 gg 函数的参数顺序与 f 函数的参数顺序相反。

具体来说,g 函数接受两个参数 xy,并将它们传递给 f 函数,但将参数顺序反转。也就是说,如果 f 函数接受 xy 作为参数,那么 g 函数将接受 yx 作为参数。

这个 flip' 函数的主要目的是将一个二元函数的参数顺序反转,使得在调用这个函数时,参数顺序与函数定义时相反。这在某些编程任务中非常有用,例如在编写接受和返回参数的函数时,将参数顺序反转可以使得函数更易于使用。

下面是一个使用 flip' 函数的例子:

1
2
3
4
5
6
7
8
9
10
-- 定义一个二元函数
double :: Int -> Int
double x = x * 2

-- 使用 flip' 函数将参数顺序反转
triple :: Int -> Int
triple x = double x x

-- 调用 triple 函数
print triple 5

输出结果为:

1
15

在这个例子中,我们定义了一个 double 函数,它接受一个整数参数并返回它的两倍。然后我们使用 flip' 函数将 double 函数的参数顺序反转,得到了 triple 函数,它接受两个整数参数并返回它们的和。最后我们调用 triple 函数并打印结果。


Haskell - 一些高级函数
http://cvrain.cloudvl.cn/2023/11/27/Haskell/haskell-hyper-function/
作者
ClaudeRainer
发布于
2023年11月27日
许可协议