Haskell: 翻转功能的目的?
Haskell: Purpose of the flip function?
我有点惊讶以前没有问过这个问题。也许这是一个愚蠢的问题。
我知道 flip 会改变两个参数的顺序。
示例:
(-) 5 3
= 5 - 3
= 2
flip (-) 5 3
= 3 - 5
= -2
但为什么我需要这样的功能?为什么不手动更改输入?
为什么不直接写:
(-) 3 5
= 3 - 5
= -2
人们不太可能在立即应用于两个或多个参数的函数上使用 flip
函数,但 flip
在两种情况下很有用:
如果函数被高阶传递给另一个函数,不能简单地反转调用点的参数,因为调用点在另一个函数中!例如,这两个表达式产生非常不同的结果:
ghci> foldl (-) 0 [1, 2, 3, 4]
-10
ghci> foldl (flip (-)) 0 [1, 2, 3, 4]
2
在这种情况下,我们不能交换(-)
的参数,因为我们没有直接应用(-)
; foldl
为我们申请。所以我们可以使用 flip (-)
而不是写出整个 lambda \x y -> y - x
.
此外,使用 flip
将函数部分应用于其第二个参数可能很有用。例如,我们可以使用 flip
编写一个函数,该函数使用提供列表中元素索引的构建器函数来构建无限列表:
buildList :: (Integer -> a) -> [a]
buildList = flip map [0..]
ghci> take 10 (buildList (\x -> x * x))
[0,1,4,9,16,25,36,49,64,81]
也许更频繁,当我们想要部分应用将在高阶使用的函数的第二个参数时使用它,如第一个示例:
ghci> map (flip map [1, 2, 3]) [(+ 1), (* 2)]
[[2,3,4],[2,4,6]]
有时,在这种情况下,人们不会使用 flip
,而是使用中缀语法,因为 operator sections 具有唯一的 属性,他们可以提供第一个 or 函数的第二个参数。因此,写成(`f` x)
等同于写成flip f x
。就个人而言,我认为直接写 flip
通常更容易阅读,但这是个人喜好问题。
有时您希望通过提供第二个参数来使用函数,但从其他地方获取第一个参数。例如:
map (flip (-) 5) [1..5]
虽然这也可以写成:
map (\x -> x - 5) [1..5]
另一个用例是当第二个参数很长时:
flip (-) 5 $
if odd x
then x + 1
else x
但是您总是可以使用 let
表达式来命名第一个参数计算,然后不使用 flip
。
flip
用法的一个非常有用的示例是按降序排序。您可以在 ghci
:
中查看它的工作原理
ghci> import Data.List
ghci> :t sortBy
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
ghci> :t compare
compare :: Ord a => a -> a -> Ordering
ghci> sortBy compare [2,1,3]
[1,2,3]
ghci> sortBy (flip compare) [2,1,3]
[3,2,1]
我有点惊讶以前没有问过这个问题。也许这是一个愚蠢的问题。
我知道 flip 会改变两个参数的顺序。
示例:
(-) 5 3
= 5 - 3
= 2
flip (-) 5 3
= 3 - 5
= -2
但为什么我需要这样的功能?为什么不手动更改输入?
为什么不直接写:
(-) 3 5
= 3 - 5
= -2
人们不太可能在立即应用于两个或多个参数的函数上使用 flip
函数,但 flip
在两种情况下很有用:
如果函数被高阶传递给另一个函数,不能简单地反转调用点的参数,因为调用点在另一个函数中!例如,这两个表达式产生非常不同的结果:
ghci> foldl (-) 0 [1, 2, 3, 4] -10 ghci> foldl (flip (-)) 0 [1, 2, 3, 4] 2
在这种情况下,我们不能交换
(-)
的参数,因为我们没有直接应用(-)
;foldl
为我们申请。所以我们可以使用flip (-)
而不是写出整个 lambda\x y -> y - x
.此外,使用
flip
将函数部分应用于其第二个参数可能很有用。例如,我们可以使用flip
编写一个函数,该函数使用提供列表中元素索引的构建器函数来构建无限列表:buildList :: (Integer -> a) -> [a] buildList = flip map [0..]
ghci> take 10 (buildList (\x -> x * x)) [0,1,4,9,16,25,36,49,64,81]
也许更频繁,当我们想要部分应用将在高阶使用的函数的第二个参数时使用它,如第一个示例:
ghci> map (flip map [1, 2, 3]) [(+ 1), (* 2)] [[2,3,4],[2,4,6]]
有时,在这种情况下,人们不会使用
flip
,而是使用中缀语法,因为 operator sections 具有唯一的 属性,他们可以提供第一个 or 函数的第二个参数。因此,写成(`f` x)
等同于写成flip f x
。就个人而言,我认为直接写flip
通常更容易阅读,但这是个人喜好问题。
有时您希望通过提供第二个参数来使用函数,但从其他地方获取第一个参数。例如:
map (flip (-) 5) [1..5]
虽然这也可以写成:
map (\x -> x - 5) [1..5]
另一个用例是当第二个参数很长时:
flip (-) 5 $
if odd x
then x + 1
else x
但是您总是可以使用 let
表达式来命名第一个参数计算,然后不使用 flip
。
flip
用法的一个非常有用的示例是按降序排序。您可以在 ghci
:
ghci> import Data.List
ghci> :t sortBy
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
ghci> :t compare
compare :: Ord a => a -> a -> Ordering
ghci> sortBy compare [2,1,3]
[1,2,3]
ghci> sortBy (flip compare) [2,1,3]
[3,2,1]