什么是高阶函数的一些有趣的用法?

我目前正在做一个函数式编程课程,我很高兴高阶函数和函数作为一等公民的概念。 然而,我还不能想到许多实际上有用的,概念上的惊人的,或者只是简单有趣的高阶函数。 (除了典型和相当沉闷的mapfilter等function)。

你知道这样有趣的function的例子吗?

也许返回函数的函数,返回函数列表(?)等的函数

我会很感激Haskell的例子,这是我目前正在学习的语言:)

那么,你注意到Haskell没有循环的语法? 没有whiledofor 。 因为这些都是更高级的function:

  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] 

更高级的函数取代了控制结构语言中的烘焙语法的需要,这意味着几乎每个Haskell程序都使用这些函数 – 使它们非常有用!

它们是实现良好抽象的第一步,因为我们现在可以将自定义行为插入到通用骨架函数中。

尤其是, 单子只有可能,因为我们可以链接在一起,操纵function,创build程序。

事实是,一生中的生活是无聊的。 一旦你拥有更高的订单,编程才会变得有趣。

面向对象编程中使用的许多技术是缺less高阶函数的解决方法。

这包括一些在函数式编程中无处不在的devise模式 。 例如,访客模式是实现折叠的一种相当复杂的方式。 解决方法是创build一个具有方法的类,并将该类的元素作为参数传入,作为传递函数的替代方法。

战略模式是一个计划的另一个例子,这个计划经常把对象作为参数来代替实际上预期的function。

类似的dependency injection往往涉及一些笨拙的scheme来传递函数的代理,当通常直接作为参数直接传递函数通常会更好。

所以我的答案是,高阶函数经常被用来执行面向对象程序员执行的相同types的任务,但是直接地,并且使用更less的样板。

当我学会了一个函数可以成为数据结构的一部分的时候,我真的开始感受到这种力量。 这是一个“消费者monad”(technobabble:免费monad over (i ->) )。

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

所以一个Coro可以立即产生一个价值,或者根据一些input是另一个Coro。 例如,这是一个Coro Int Int

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

这消耗三个整数input并返回它们的总和。 根据input你也可以有不同的performance:

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

这会消耗一个Int,然后在得到它们的总和之前消耗更多的Ints。 这可以被认为是一个function,任意多个参数,没有任何语言魔术,只是更高阶的function。

数据结构中的函数是一个非常强大的工具,在我开始做Haskell之前,这不是我的词汇的一部分。

看看这篇文章“parsing高阶函数”或者“为什么要使用六阶函数”? 由Chris Okasaki。 它是用ML编写的,但这些想法同样适用于Haskell。

Joel Spolsky写了一篇着名的文章,演示了Map-Reduce如何使用Javascript的高阶函数。 任何人提出这个问题都必须阅读。

更高阶的function也需要咖喱 ,这是Haskell随处可见的。 本质上,一个带两个参数的函数相当于一个函数带一个参数,另一个函数带一个参数。 当你在Haskell中看到这样的types签名时:

 f :: A -> B -> C 

(->)可以被看作是右关联的,表明这实际上是一个返回typesB -> C的函数的高阶函数:

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

两个参数的非curry函数会有这样的types:

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

所以,当你在Haskell中使用部分应用程序时,你正在使用更高阶的函数。

MartínEscardó提供了一个高阶函数的有趣例子 :

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

给定两个函数f, g :: (Integer -> Bool) -> Int ,则equal fg决定fg是否(扩展)相等或不相等,即使fg没有有限域。 实际上,可以用任何可判定的等式来替代codomain, Int

Escardó给出的代码是用Haskell编写的,但是相同的algorithm应该可以在任何函数式语言中使用。

您可以使用与Escardó描述的相同的技术来计算任意连续函数的定积分以达到任意精度。

你可以做一个有趣而有点疯狂的事情,就是使用一个函数来模拟一个面向对象的系统 ,并将数据存储在函数的作用域(即闭包)中。 对象生成器函数是返回对象(另一个函数)的函数,它是高阶的。

我的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))))) 

用法:

 (def a (make-object 10)) (a :get) => 10 (a :set 40) (a :get) => 40 

同样的原则可以在Haskell中工作(除了你可能需要改变set操作返回一个新的函数,因为Haskell是纯粹的function)

我是高级记忆的特别粉丝:

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

(给定任何函数,返回该函数的备忘录版本,受限于函数的参数必须能够被编码为一个trie)。

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

这里有几个例子: http : //www.haskell.org/haskellwiki/Higher_order_function

我也推荐这本书: http : //www.cs.nott.ac.uk/~gmh/book.html这是一个很好的介绍所有的Haskell和涵盖更高阶的function。

高阶函数经常使用累加器,所以在形成符合给定规则的元素列表时,可以使用更大的列表。

这是一个小小的转述代码片段:

 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 

这使用了几个“更高阶”的function:

 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 => ma -> (a -> mb) -> mb 

(.). (>>=)do -notation“换行符”。

在Haskell中编程时,只需使用它们。 如果你没有更高级的function,那么当你意识到它们是多么的有用的时候。

这是我还没有看到其他人提到的模式,第一次了解到这一点,真的让我感到吃惊。 考虑一个统计软件包,你有一个样本列表作为你的input,你想计算一堆不同的统计数据(也有很多其他的方法来激励这个)。 底线是你有一个你想运行的function列表。 你如何运行它们?

 statFuncs :: [ [Double] -> Double ] statFuncs = [minimum, maximum, mean, median, mode, stddev] runWith funcs samples = map ($samples) funcs 

这里有各种更高级的善意,其中一些在其他答案中已经提到。 但是我想指出'$'函数。 当我第一次看到这个'$'的使用,我被吹走了。 在此之前,我不认为这是一个非常有用的方法,而不是一个方便的替代括号……但这几乎是神奇的…

有一点很有趣,如果不是特别实用,那就是Church Numerals 。 这是一种只用函数表示整数的方法。 疯狂,我知道。 <shamelessPlug>这是我在JavaScript中的一个实现 。 比Lisp / Haskell实现更容易理解。 (但可能不是,说实话,JavaScript并不是真正意义上的这种东西。)</ shamelessPlug>

有人提到,Javascript支持某些更高级的函数,包括Joel Spolsky的一篇文章 。 Mark Jason Dominus撰写了一本叫做Higher-Order Perl的书 , 本书的源代码可以免费下载,包括PDF在内的各种精细格式。

至less从Perl 3开始,Perl支持的function比C更让人联想到Lisp,但是直到Perl 5完全支持闭包以及所有后续的function之后,才有了它。 第一个Perl 6的实现是用Haskell编写的,它对这个语言的devise进展有很大的影响。

Perl中的函数式编程方法的例子出现在日常编程中,特别是在mapgrep

 @ARGV = map { /\.gz$/ ? "gzip -dc < $_ |" : $_ } @ARGV; @unempty = grep { defined && length } @many; 

由于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/*"); 

要么

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

reducefunction使列表hackery容易没有循环:

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

还有比这更多的,但这只是一个口味。 闭包可以很容易地创build函数发生器,编写自己的高阶函数,而不仅仅是使用内置函数。 事实上,比较常见的exception模型之一,

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

不是内置的。 然而,它几乎被平凡的定义为try函数,它有两个参数:第一个arg中的闭包和第二个中的闭包。

Perl 5没有内置的currying,虽然有一个模块。 不过,Perl 6拥有内置的柯式和一stream的延续,再加上更多。