[英]What are some interesting uses of higher-order functions?
我目前正在上一門函數編程課程,並且對高階函數和作為一等公民的函數的概念感到很開心。 但是,我還不能想到許多實用,概念上令人驚奇的東西,或者僅僅是有趣的高階函數。 (除了典型的,相當乏味的map
, filter
等功能之外)。
您知道此類有趣功能的示例嗎?
也許返回函數的函數,返回函數列表(?)的函數等。
我很欣賞Haskell中的示例,這是我目前正在學習的語言:)
好吧,您注意到Haskell沒有循環的語法嗎? 沒有while
或do
或for
。 因為這些都是高階函數:
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程序都使用這些函數-使其非常有用!
它們是實現良好抽象的第一步,因為我們現在可以將自定義行為插入通用框架功能中。
特別是單子函數是唯一可行的方法,因為我們可以將它們鏈接在一起並操縱函數來創建程序。
事實是,生活是無聊的。 編程只有在您具有更高階時才會變得有趣。
當我知道一個函數可以成為數據結構的一部分時,我真的開始感受到力量。 這是一個“消費者單子”(technobabble:在(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之前,這並不是我的詞匯表的一部分。
查閱論文“甚至用於解析的高階函數,或者為什么有人要使用六階函數?” 克里斯·岡崎(Chris Okasaki)。 它是使用ML編寫的,但是這些想法同樣適用於Haskell。
喬爾·斯波斯基(Joel Spolsky)寫了一篇著名的文章,論證了Map-Reduce如何使用Javascript的高階函數工作。 任何人問這個問題必讀。
Haskell在所有地方都使用高階函數進行currying 。 本質上,具有兩個參數的函數等效於具有一個參數並返回具有一個參數的另一個函數的函數。 當您在Haskell中看到這樣的類型簽名時:
f :: A -> B -> C
... (->)
可以看作是右關聯的,表明實際上這是一個返回B -> C
類型的函數的高階函數:
f :: A -> (B -> C)
具有兩個參數的非咖喱函數將改為具有以下類型:
f' :: (A, B) -> C
因此,無論何時在Haskell中使用部分應用程序,都在使用高階函數。
MartínEscardó提供了一個有趣的高階函數示例 :
equal :: ((Integer -> Bool) -> Int) -> ((Integer -> Bool) -> Int) -> Bool
給定兩個函數f, g :: (Integer -> Bool) -> Int
,則equal fg
決定f
和g
是否(在擴展上)相等,即使f
和g
沒有有限域。 實際上,共域Int
可以由具有可確定相等性的任何類型替換。
Escardó提供的代碼是用Haskell編寫的,但是相同的算法應該可以在任何功能語言中使用。
您可以使用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純粹是功能性的)
我特別喜歡高階記憶:
memo :: HasTrie t => (t -> a) -> (t -> a)
(給出任何函數,返回該函數的備注版本。受該函數的自變量必須能夠被編碼為特里的事實的限制。)
這里有幾個示例: http : //www.haskell.org/haskellwiki/Higher_order_function
我還會推薦這本書: http : //www.cs.nott.ac.uk/~gmh/book.html ,它是對所有Haskell的出色介紹,並涵蓋了高階函數。
高階函數通常使用累加器,因此可以在從較大列表中形成符合給定規則的元素列表時使用。
這是一個簡短的代碼段:
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中編程時,只需使用它們即可。 當您意識到高階函數非常有用時,便沒有它們了。
這是我從未見過其他人提及的一種模式,這使我第一次了解它時確實感到驚訝。 考慮一個統計數據包,其中有一個樣本列表作為您的輸入,並且您想計算出一系列關於它們的統計數據(還有許多其他方法可以激發這一點)。 最重要的是,您具有要運行的功能的列表。 您如何全部運行?
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)寫了一本書,名為《 高階Perl》 ; 本書的源代碼可以以各種精美格式免費下載,包括PDF 。
從至少Perl 3開始,Perl就一直支持Lisp而不是C的功能,但是直到Perl 5才提供對閉包的完全支持以及從中獲得的所有支持。 Perl 6的第一個實現中的ne是用Haskell編寫的,這對該語言的設計發展有很大的影響。
Perl中的函數式編程方法示例在日常編程中都會出現,尤其是map
和grep
:
@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
功能使列表黑客變得輕松而無需循環:
$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內置了可彎曲和一流的延續功能,還有更多功能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.