简体   繁体   English

高阶函数有哪些有趣的用途?

[英]What are some interesting uses of higher-order functions?

I'm currently doing a Functional Programming course and I'm quite amused by the concept of higher-order functions and functions as first class citizens. 我目前正在上一门函数编程课程,并且对高阶函数和作为一等公民的函数的概念感到很开心。 However, I can't yet think of many practically useful, conceptually amazing, or just plain interesting higher-order functions. 但是,我还不能想到许多实用,概念上令人惊奇的东西,或者仅仅是有趣的高阶函数。 (Besides the typical and rather dull map , filter , etc functions). (除了典型的,相当乏味的mapfilter等功能之外)。

Do you know examples of such interesting functions? 您知道此类有趣功能的示例吗?

Maybe functions that return functions, functions that return lists of functions (?), etc. 也许返回函数的函数,返回函数列表(?)的函数等。

I'd appreciate examples in Haskell, which is the language I'm currently learning :) 我很欣赏Haskell中的示例,这是我目前正在学习的语言:)

Well, you notice that Haskell has no syntax for loops? 好吧,您注意到Haskell没有循环的语法吗? No while or do or for . 没有whiledofor Because these are all just higher-order functions: 因为这些都是高阶函数:

 map :: (a -> b) -> [a] -> [b]

 foldr :: (a -> b -> b) -> b -> [a] -> b

 filter :: (a -> Bool) -> [a] -> [a]

 unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

 iterate :: (a -> a) -> a -> [a]

Higher-order functions replace the need for baked in syntax in the language for control structures, meaning pretty much every Haskell program uses these functions -- making them quite useful! 高阶函数取代了对控制结构语言的语法烘焙要求,这意味着几乎每个Haskell程序都使用这些函数-使其非常有用!

They are the first step towards good abstraction because we can now plug custom behavior into a general purpose skeleton function. 它们是实现良好抽象的第一步,因为我们现在可以将自定义行为插入通用框架功能中。

In particular, monads are only possible because we can chain together, and manipulate functions, to create programs. 特别是单子函数是唯一可行的方法,因为我们可以将它们链接在一起并操纵函数来创建程序。

The fact is, life is pretty boring when it is first-order. 事实是,生活是无聊的。 Programming only gets interesting once you have higher-order. 编程只有在您具有更高阶时才会变得有趣。

Many techniques used in OO programming are workarounds for the lack of higher order functions. OO编程中使用的许多技术都是缺少高阶函数的解决方法。

This includes a number of the design patterns that are ubiquitous in functional programming. 这包括功能编程中无处不在的许多设计模式 For example, the visitor pattern is a rather complicated way to implement a fold . 例如,visitor模式是实现fold的一种相当复杂的方法。 The workaround is to create a class with methods and pass in an element of the class in as an argument, as a substitute for passing in a function. 解决方法是使用方法创建一个类,并将该类的元素作为参数传入,以替代传入函数。

The strategy pattern is another example of a scheme that often passes objects as arguments as a substitute for what is actually intended, functions. 策略模式是该方案的另一个示例,该方案通常将对象作为参数传递,以代替实际意图的功能。

Similarly dependency injection often involves some clunky scheme to pass a proxy for functions when it would often be better to simply pass in the functions directly as arguments. 类似地, 依赖注入通常涉及一些笨拙的方案来传递函数的代理,而直接将函数作为参数传递通常会更好。

So my answer would be that higher-order functions are often used to perform the same kinds of tasks that OO programmers perform, but directly, and with a lot less boilerplate. 因此,我的回答是,通常使用高阶函数来执行与OO程序员所执行的相同类型的任务,但是要直接执行,而要花的时间少得多。

I really started to feel the power when I learned a function can be part of a data structure. 当我知道一个函数可以成为数据结构的一部分时,我真的开始感受到力量。 Here is a "consumer monad" (technobabble: free monad over (i ->) ). 这是一个“消费者单子”(technobabble:在(i ->)免费的单子)。

data Coro i a
    = Return a
    | Consume (i -> Coro i a)

So a Coro can either instantly yield a value, or be another Coro depending on some input. 因此, Coro可以立即产生一个值,或者根据某些输入成为另一个Coro。 For example, this is a Coro Int Int : 例如,这是Coro Int Int

Consume $ \x -> Consume $ \y -> Consume $ \z -> Return (x+y+z)

This consumes three integer inputs and returns their sum. 这消耗了三个整数输入并返回它们的总和。 You could also have it behave differently according to the inputs: 您还可以根据输入使它的行为有所不同:

sumStream :: Coro Int Int
sumStream = Consume (go 0)
    where
    go accum 0 = Return accum
    go accum n = Consume (\x -> go (accum+x) (n-1))

This consumes an Int and then consumes that many more Ints before yielding their sum. 这将消耗一个Int,然后在产生其和之前消耗更多的Int。 This can be thought of as a function that takes arbitrarily many arguments, constructed without any language magic, just higher order functions. 可以认为这是一个函数,它可以任意接收许多参数,而无需任何语言魔术就可以构造这些参数,而只是高阶函数。

Functions in data structures are a very powerful tool that was not part of my vocabulary before I started doing Haskell. 数据结构中的函数是一个非常强大的工具,在开始执行Haskell之前,这并不是我的词汇表的一部分。

Check out the paper 'Even Higher-Order Functions for Parsing or Why Would Anyone Ever Want To Use a Sixth-Order Function?' 查阅论文“甚至用于解析的高阶函数,或者为什么有人要使用六阶函数?” by Chris Okasaki. 克里斯·冈崎(Chris Okasaki)。 It's written using ML, but the ideas apply equally to Haskell. 它是使用ML编写的,但是这些想法同样适用于Haskell。

Joel Spolsky wrote a famous essay demonstrating how Map-Reduce works using Javascript's higher order functions. 乔尔·斯波斯基(Joel Spolsky)写了一篇著名的文章,论证了Map-Reduce如何使用Javascript的高阶函数工作。 A must-read for anyone asking this question. 任何人问这个问题必读。

Higher-order functions are also required for currying , which Haskell uses everywhere. Haskell在所有地方都使用高阶函数进行currying Essentially, a function taking two arguments is equivalent to a function taking one argument and returning another function taking one argument. 本质上,具有两个参数的函数等效于具有一个参数并返回具有一个参数的另一个函数的函数。 When you see a type signature like this in Haskell: 当您在Haskell中看到这样的类型签名时:

f :: A -> B -> C

...the (->) can be read as right-associative, showing that this is in fact a higher-order function returning a function of type B -> C : ... (->)可以看作是右关联的,表明实际上这是一个返回B -> C类型的函数的高阶函数:

f :: A -> (B -> C)

A non-curried function of two arguments would instead have a type like this: 具有两个参数的非咖喱函数将改为具有以下类型:

f' :: (A, B) -> C

So any time you use partial application in Haskell, you're working with higher-order functions. 因此,无论何时在Haskell中使用部分应用程序,都在使用高阶函数。

Martín Escardó provides an interesting example of a higher-order function : MartínEscardó提供了一个有趣的高阶函数示例

equal :: ((Integer -> Bool) -> Int) -> ((Integer -> Bool) -> Int) -> Bool

Given two functionals f, g :: (Integer -> Bool) -> Int , then equal fg decides if f and g are (extensionally) equal or not, even though f and g don't have a finite domain. 给定两个函数f, g :: (Integer -> Bool) -> Int ,则equal fg决定fg是否(在扩展上)相等,即使fg没有有限域。 In fact, the codomain, Int , can be replaced by any type with a decidable equality. 实际上,共域Int可以由具有可确定相等性的任何类型替换。

The code Escardó gives is written in Haskell, but the same algorithm should work in any functional language. Escardó提供的代码是用Haskell编写的,但是相同的算法应该可以在任何功能语言中使用。

You can use the same techniques that Escardó describes to compute definite integrals of any continuous function to arbitrary precision. 您可以使用Escardó描述的相同技术,以任意精度计算任何连续函数的定积分。

One interesting and slightly crazy thing you can do is simulate an object-oriented system using a function and storing data in the function's scope (ie in a closure). 您可以做的一件有趣且有点疯狂的事情是使用一个函数模拟一个面向对象的系统 ,并将数据存储在该函数的作用域内(即闭包中)。 It's higher-order in the sense that the object generator function is a function which returns the object (another function). 从某种意义上讲,对象生成器函数是一个返回对象的函数(另一个函数),它是高阶的。

My Haskell is rather rusty so I can't easily give you a Haskell example, but here's a simplified Clojure example which hopefully conveys the concept: 我的Haskell相当生锈,因此我不能轻易地给您一个Haskell示例,但这是一个简化的Clojure示例,希望可以传达这个概念:

(defn make-object [initial-value]
  (let [data (atom {:value initial-value})]
      (fn [op & args]
        (case op 
          :set (swap! data assoc :value (first args))
          :get (:value @data)))))

Usage: 用法:

(def a (make-object 10))

(a :get)
=> 10

(a :set 40)

(a :get)
=> 40

Same principle would work in Haskell (except that you'd probably need to change the set operation to return a new function since Haskell is purely functional) 同样的原理也可以在Haskell中使用(除非您可能需要更改set操作以返回新函数,因为Haskell纯粹是功能性的)

I'm a particular fan of higher-order memoization: 我特别喜欢高阶记忆:

memo :: HasTrie t => (t -> a) -> (t -> a)

(Given any function, return a memoized version of that function. Limited by the fact that the arguments of the function must be able to be encoded into a trie.) (给出任何函数,返回该函数的备注版本。受该函数的自变量必须能够被编码为特里的事实的限制。)

This is from http://hackage.haskell.org/package/MemoTrie 这是从http://hackage.haskell.org/package/MemoTrie

There are several examples here: http://www.haskell.org/haskellwiki/Higher_order_function 这里有几个示例: http : //www.haskell.org/haskellwiki/Higher_order_function

I would also recommend this book: http://www.cs.nott.ac.uk/~gmh/book.html which is a great introduction to all of Haskell and covers higher order functions. 我还会推荐这本书: http : //www.cs.nott.ac.uk/~gmh/book.html ,它是对所有Haskell的出色介绍,并涵盖了高阶函数。

Higher order functions often use an accumulator so can be used when forming a list of elements which conform to a given rule from a larger list. 高阶函数通常使用累加器,因此可以在从较大列表中形成符合给定规则的元素列表时使用。

Here's a small paraphrased code snippet: 这是一个简短的代码段:

rays :: ChessPieceType -> [[(Int, Int)]]
rays Bishop = do
  dx <- [1, -1]
  dy <- [1, -1]
  return $ iterate (addPos (dx, dy)) (dx, dy)
...  -- Other piece types

-- takeUntilIncluding is an inclusive version of takeUntil
takeUntilIncluding :: (a -> Bool) -> [a] -> [a]

possibleMoves board piece = do
  relRay <- rays (pieceType piece)
  let ray = map (addPos src) relRay
  takeUntilIncluding (not . isNothing . pieceAt board)
    (takeWhile notBlocked ray)
  where
    notBlocked pos =
      inBoard pos &&
      all isOtherSide (pieceAt board pos)
    isOtherSide = (/= pieceSide piece) . pieceSide

This uses several "higher order" functions: 这使用了几个“高阶”功能:

iterate :: (a -> a) -> a -> [a]
takeUntilIncluding  -- not a standard function
takeWhile :: (a -> Bool) -> [a] -> [a]
all :: (a -> Bool) -> [a] -> Bool
map :: (a -> b) -> [a] -> [b]
(.) :: (b -> c) -> (a -> b) -> a -> c
(>>=) :: Monad m => m a -> (a -> m b) -> m b

(.) is the . (.). operator, and (>>=) is the do -notation "line break operator". 运算符,而(>>=)do标记“换行符”。

When programming in Haskell you just use them. 在Haskell中编程时,只需使用它们即可。 Where you don't have the higher order functions is when you realize just how incredibly useful they were. 当您意识到高阶函数非常有用时,便没有它们了。

Here's a pattern that I haven't seen anyone else mention yet that really surprised me the first time I learned about it. 这是我从未见过其他人提及的一种模式,这使我第一次了解它时确实感到惊讶。 Consider a statistics package where you have a list of samples as your input and you want to calculate a bunch of different statistics on them (there are also plenty of other ways to motivate this). 考虑一个统计数据包,其中有一个样本列表作为您的输入,并且您想计算出一系列关于它们的统计数据(还有许多其他方法可以激发这一点)。 The bottom line is that you have a list of functions that you want to run. 最重要的是,您具有要运行的功能的列表。 How do you run them all? 您如何全部运行?

statFuncs :: [ [Double] -> Double ]
statFuncs = [minimum, maximum, mean, median, mode, stddev]

runWith funcs samples = map ($samples) funcs

There's all kinds of higher order goodness going on here, some of which has been mentioned in other answers. 这里有各种各样的高阶善,其中一些已经在其他答案中提到。 But I want to point out the '$' function. 但我想指出“ $”功能。 When I first saw this use of '$', I was blown away. 当我第一次看到“ $”的用法时,我被震撼了。 Before that I hadn't considered it to be very useful other than as a convenient replacement for parentheses...but this was almost magical... 在那之前,我不认为它是非常有用的,除了可以方便地代替括号...但这几乎是神奇的...

One thing that's kind of fun, if not particularly practical, is Church Numerals . 有趣的一件事是教堂数字Church Numerals) ,即使不是特别实用。 It's a way of representing integers using nothing but functions. 这是一种只使用函数表示整数的方法。 Crazy, I know. 疯狂,我知道。 <shamelessPlug>Here's an implementation in JavaScript that I made. <shamelessPlug>这是我制作的JavaScript实现 It might be easier to understand than a Lisp/Haskell implementation. 它可能比Lisp / Haskell实现更容易理解。 (But probably not, to be honest. JavaScript wasn't really meant for this kind of thing.)</shamelessPlug> (但是,老实说,可能不是。JavaScript并非真的适用于这种情况。)</ shamelessPlug>

It's been mentioned that Javascript supports certain higher-order functions, including an essay from Joel Spolsky . 有人提到Javascript支持某些高阶函数,包括Joel Spolsky的文章 Mark Jason Dominus wrote an entire book called Higher–Order Perl ; 马克·杰森·多米努斯(Mark Jason Dominus)写了一本书,名为《 高阶Perl》 the book's source is available for free download in a variety of fine formats, include PDF . 本书的源代码可以以各种精美格式免费下载,包括PDF

Ever since at least Perl 3, Perl has supported functionality more reminiscent of Lisp than of C, but it wasn't until Perl 5 that full support for closures and all that follows from that was available. 从至少Perl 3开始,Perl就一直支持Lisp而不是C的功能,但是直到Perl 5才提供对闭包的完全支持以及从中获得的所有支持。 And ne of the first Perl 6 implementations was written in Haskell, which has had a lot of influence on how that language's design has progressed. Perl 6的第一个实现中的ne是用Haskell编写的,这对该语言的设计发展有很大的影响。

Examples of functional programming approaches in Perl show up in everyday programming, especially with map and grep : Perl中的函数式编程方法示例在日常编程中都会出现,尤其是mapgrep

@ARGV    = map { /\.gz$/ ? "gzip -dc < $_ |" : $_ } @ARGV;

@unempty = grep { defined && length } @many;

Since sort also admits a closure, the map/sort/map pattern is super common: 由于sort也允许闭包,因此map/sort/map模式非常常见:

@txtfiles = map { $_->[1] }
            sort { 
                    $b->[0]  <=>     $a->[0]
                              ||
                 lc $a->[1]  cmp  lc $b->[1]
                              ||
                    $b->[1]  cmp     $a->[1]
            }
            map  { -s => $_ } 
            grep { -f && -T }
            glob("/etc/*");

or 要么

@sorted_lines = map { $_->[0] }
                sort {
                     $a->[4] <=> $b->[4] 
                             ||
                    $a->[-1] cmp $b->[-1]
                             ||
                     $a->[3] <=> $b->[3]
                             ||
                     ...
                }
                map { [$_ => reverse split /:/] } @lines;

The reduce function makes list hackery easy without looping: reduce功能使列表黑客变得轻松而无需循环:

$sum = reduce { $a + $b } @numbers;

$max = reduce { $a > $b ? $a : $b } $MININT, @numbers;

There's a lot more than this, but this is just a taste. 还有很多,但这只是一种味道。 Closures make it easy to create function generators, writing your own higher-order functions, not just using the builtins. 闭包使创建函数生成器,编写自己的高阶函数(而不仅仅是使用内置函数)变得容易。 In fact, one of the more common exception models, 实际上,一种较常见的异常模型

try {
   something();
} catch {
   oh_drat();
};

is not a built-in. 不是内置的。 It is, however, almost trivially defined with try being a function that takes two arguments: a closure in the first arg and a function that takes a closure in the second one. 但是,它几乎没有定义, try是一个带有两个参数的函数:第一个arg中的闭包和第二个参数中的闭包。

Perl 5 doesn't have have currying built-in, although there is a module for that. 尽管有一个模块,Perl 5却没有内置currying。 Perl 6, though, has currying and first-class continuations built right into it, plus a lot more. 不过,Perl 6内置了可弯曲和一流的延续功能,还有更多功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM