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

时间:2011-04-26 15:38:18

标签: function haskell functional-programming combinators higher-order-functions

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

你知道这些有趣功能的例子吗?

可能是返回函数的函数,返回函数列表的函数(?)等。

我很欣赏Haskell中的例子,这是我目前正在学习的语言:)

14 个答案:

答案 0 :(得分:45)

嗯,你注意到Haskell没有循环语法?没有whiledofor。因为这些只是高阶函数:

 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程序都使用这些函数 - 使它们非常有用!

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

特别是monads是可能的,因为我们可以链接在一起,并操纵函数来创建程序。

事实是,当它是一阶时,生活很无聊。一旦你有了更高阶,编程才会变得有趣。

答案 1 :(得分:37)

OO编程中使用的许多技术都是缺乏高阶函数的解决方法。

这包括一些在函数式编程中无处不在的design patterns。例如,访问者模式是实现fold的一种相当复杂的方式。解决方法是使用方法创建一个类,并将类的元素作为参数传入,作为传入函数的替代。

strategy模式是一个方案的另一个例子,它经常将对象作为参数传递,以替代实际意图的函数。

类似地,dependency injection通常涉及一些笨重的方案来传递函数的代理,而通常更好的是直接将函数作为参数传递。

所以我的答案是高阶函数通常用于执行OO程序员执行的相同类型的任务,但是直接执行,并且使用很少的样板。

答案 2 :(得分:15)

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

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

因此Coro可以立即产生一个值,或者根据某些输入成为另一个Coro。例如,这是Coro Int Int

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

这会消耗三个整数输入并返回它们的总和。您还可以根据输入使其行为不同:

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,然后在产生它们的总和之前消耗更多的Int。这可以被认为是一个函数,它接受任意多个参数,没有任何语言魔法,只是更高阶函数。

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

答案 3 :(得分:11)

查看Chris Okasaki的论文'Even Higher-Order Functions for Parsing or Why Would Anyone Ever Want To Use a Sixth-Order Function?'。它是用ML编写的,但这些想法同样适用于Haskell。

答案 4 :(得分:9)

Joel Spolsky写了一篇famous essay,演示了Map-Reduce如何使用Javascript的高阶函数。对于提出这个问题的人来说,必读。

答案 5 :(得分:7)

currying 也需要高阶函数,Haskell在任何地方都使用它。本质上,一个带两个参数的函数相当于一个函数接受一个参数并返回另一个函数接受一个参数。当你在Haskell中看到这样的类型签名时:

f :: A -> B -> C

... (->)可以被视为右关联,表明这实际上是一个返回类型为B -> C的函数的高阶函数:

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

两个参数的非curried函数将改为具有如下类型:

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

因此,只要您在Haskell中使用部分应用程序,就可以使用更高阶的函数。

答案 6 :(得分:6)

MartínEscardó提供an interesting example of a higher-order function

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

鉴于两个函数f, g :: (Integer -> Bool) -> Intequal f g决定fg是否(扩展性)相等或不相等,即使f和{{1}没有有限的域。实际上,codomain g可以被任何具有可判定等式的类型替换。

Escardó给出的代码是用Haskell编写的,但是相同的算法应该适用于任何函数式语言。

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

答案 7 :(得分:4)

你可以做的一件有趣且有点疯狂的事情是使用函数模拟面向对象的系统并将数据存储在函数的范围内(即在闭包中)。在对象生成器函数是返回对象的函数(另一个函数)的意义上,它是高阶的。

我的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纯粹是函数式的)

答案 8 :(得分:4)

我特别喜欢高阶记忆:

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

(给定任何函数,返回该函数的memoized版本。受限于函数的参数必须能够编码为trie的事实。)

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

答案 9 :(得分:3)

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

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

高阶函数通常使用累加器,因此可以在形成符合较大列表中给定规则的元素列表时使用。

答案 10 :(得分:3)

这是一个小的释义code摘要:

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

这使用了几个“高阶”函数:

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

(.).运算符,(>>=)do - 符号“换行符”。

在Haskell中编程时,您只需使用它们。你没有更高阶函数的地方就是当你意识到它们有多么有用时。

答案 11 :(得分:3)

这是我从未见过的其他人提到过的一种模式,在我第一次了解它时真的让我感到惊讶。考虑一个统计包,其中您有一个样本列表作为输入,并且您想要计算一堆不同的统计数据(还有很多其他方法可以激发这一点)。最重要的是,您有一个要运行的功能列表。你怎么运行它们?

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

runWith funcs samples = map ($samples) funcs

这里有各种各样的高阶善良,其中一些已经在其他答案中提到过。但我想指出'$'功能。当我第一次看到'$'的使用时,我被吹走了。在此之前我没有认为它是非常有用的,除了作为括号的方便替代...但这几乎是神奇的......

答案 12 :(得分:2)

有一件事,如果不是特别实用,那就是Church Numerals。这是一种只使用函数来表示整数的方法。疯了,我知道。 &lt; shamelessPlug&gt;这是我制作的implementation in JavaScript。它可能比Lisp / Haskell实现更容易理解。 (但可能不是,说实话.JEQ并不是真的意味着这种事情。)&lt; / shamelessPlug&gt;

答案 13 :(得分:2)

有人提到Javascript支持某些高阶函数,包括an essay from Joel Spolsky。 Mark Jason Dominus写了一本名为Higher–Order Perl的书;该书的来源可以各种精美格式免费下载,包括PDF

至少从Perl 3开始,Perl支持的功能更像是Lisp而不是C语言,但是直到Perl 5才完全支持闭包以及随之而来的所有内容。第一个Perl 6实现是用Haskell编写的,它对该语言设计的进展产生了很大的影响。

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;

reduce函数使列表hackery变得容易而不循环:

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

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

除此之外还有更多,但这只是一种品味。闭包可以轻松创建函数生成器,编写自己的高阶函数,而不仅仅是使用内置函数。实际上,是一种比较常见的异常模型,

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

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

Perl 5没有内置currying,虽然有一个模块。不过,Perl 6已经内置了curry和一流的延续,还有更多。