热门问题
时间线
聊天
视角
Map (高階函數)
来自维基百科,自由的百科全书
Remove ads
在很多編程語言中,映射(map)是一個高階函數的名字,它將一個給定函數應用到一個函子比如列表的每個元素,返回按相同次序的一個列表。映射的概念不受限於列表:它可工作在順序的容器,類似樹的容器,甚至是抽象容器比如future與promise。
映射在作為泛函形式考慮時,經常叫做「應用於全部」。在支持頭等函數和柯里化的語言中,map
可以部份應用在一個函數上,將這個只工作在一個值之上函數,提升為工作在整個容器上的逐個元素上的函數。
定義
map
作為Haskell的基本前序(prelude:就是標準庫)的一部份提供並被實現為:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x : xs) = f x : map f xs
在這個定義之中,涉及到四種不同的模式:
f
是匹配「無論任何事物」的模式,並綁定f
變量至所匹配的任何事物。(x : xs)
是匹配「非空列表」的模式,它是通過將要被綁定到x
變量某個事物,cons
(這裡是(:)
函數)到要被綁定到xs
變量的某個其他事物之上而形成的。[]
是匹配「空列表」的模式,它不綁定任何變量。_
是匹配不被綁定的任何事物的模式(通配符即「不在意」模式)。
Lisp語言在1959年介入了叫做maplist
的映射函數[1],與1958年出現的版本稍有不同[2]。這是maplist
的最初定義,將一個函數連續的映射到整個列表和去除第一個元素餘下列表之上:
maplist[x;f] = [null[x] -> NIL;T -> cons[f[x];maplist[cdr[x];f]]]
函數maplist
在更新近的Lisp比如Common Lisp中仍可獲得到[3]。Common Lisp提供映射函數家族,如mapcar
和更一般性的map
等,其中對應這裡所描述行為的那叫做mapcar
,這裡的後綴car
指示取列表第一個元素的CAR運算。
Remove ads
例子:映射一個列表
假定我們有一個整數的列表[1, 2, 3, 4, 5]
,並想要計算每個整數的平方。要完成這個任務,首先要定義一個函數square
來做單一數值的平方,下面用Haskell演示:
square x = x * x
然後就可以調用:
>>> map square [1, 2, 3, 4, 5]
它產生[1, 4, 9, 16, 25]
,展示了map
遍歷了整個列表並應用函數square
於每個元素。在ML、Haskell和F#中,函數是缺省以柯里化形式定義的,從而map square
是對列表的每個元素做平方的Haskell函數。
在Lisp中,使用mapcar
對列表的元素做平方,使用S-表達式表示法寫為:
(mapcar (function sqr) '(1 2 3 4 5))
使用函數maplist
,上例將寫為:
(maplist (lambda (l) (sqr (car l))) '(1 2 3 4 5))
Remove ads
下面將看到一個映射過程的每一步驟的可視演示,它依據函數將整數列表X = [0, 5, 8, 3, 2, 1]
映射成新的列表X'
:

推廣
在Haskell中,多態函數map :: (a -> b) -> [a] -> [b]
,被推廣成多類型函數fmap :: Functor f => (a -> b) -> f a -> f b
,它應用於屬於函子Functor
類型類的任何類型上。
列表的類型構造子[]
可以定義為Functor
類型類的實例,使用上例中的map
函數:
instance Functor [] where
fmap = map
其他Functor
實例的例子包括樹:
-- 一个简单的二叉树
data Tree a = Leaf a | Fork (Tree a) (Tree a)
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Fork l r) = Fork (fmap f l) (fmap f r)
在一個樹之上的映射產生:
>>> fmap square (Fork (Fork (Leaf 1) (Leaf 2)) (Fork (Leaf 3) (Leaf 4)))
Fork (Fork (Leaf 1) (Leaf 4)) (Fork (Leaf 9) (Leaf 16))
對於Functor
類型類的所有實例,fmap
都在契約義務上要滿足函子定律:
fmap id ≡ id -- 同一律
fmap (f . g) ≡ fmap f . fmap g -- 复合律
這裡的.
在Haskell中指示函數複合。
尤其是,它允許為各種搜集定義逐個元素的運算。
此外,如果F和G是兩個函子,自然變換是多態類型的函數,它遵守:
- ,對於任何函數。
如果函數是按上述類型定義那樣用參數多態定義的,這個規定總是滿足的。
Remove ads
優化
映射的數學基礎允許很多優化。複合定律確保了如下二者:
(map f . map g) list
map (f . g) list
導致相同的結果;就是說。但是第二種形式比第一種形式在計算上更加有效率,因為每個map
要求從頭重建整個列表。因此,編譯器將嘗試將第一種形式變換第二種形式;這種類型的優化叫做「映射融合」,是循環融合的函數式類似者[4]。
映射函數可以並經常依據fold比如foldr
來定義,這意味着可以做「map-fold融合」:foldr f z . map g
等價於foldr (f . g) z
。
在一個單鍊表上的map實現不是尾遞歸的,所以它在調用於大型列表的時候,可能在棧上建造大量的幀。很多語言作為替代的提供「逆向map」函數,它等價於逆轉一個映射後的列表,但它是尾遞歸的。下面是利用了左fold函數的一個實現:
reverseMap f = foldl (\ys x -> f x : ys) []
因為逆轉單鍊表也是尾遞歸的,reverse和reverse-map可以複合起來以尾遞歸方式進行正常的map,儘管這需要在這個列表上進行兩趟。
Remove ads
語言比較
映射函數出現在函數式編程語言之中。現在映射函數在很多過程式、面向對象和多范型語言中也能獲得到(或能夠定義):在C++的標準模板庫中,它叫做std::transform
,在C#(3.0)的LINQ庫中,它被作為叫做Select
的擴展方法。映射也經常在高級語言中的計算,比如ColdFusion標記語言(CFML)、Perl、Python和Ruby;在這四種語言中這個運算都叫做map
。在Ruby中還提供collect
作為map
的別名(來自Smalltalk)。有些語言通過語法構造提供與映射函數相同的功能。
映射有時被推廣為接收二元的(2個參數)函數,它可以把用戶提供的函數應用到來自兩個列表的對應元素上。有些語言對它使用特殊名字,比如「map2」或「zipWith」。使用顯式的可變元數函數的語言擁有可變元數版本的映射來支持可變元數函數。有2個或更多列表在這些列表有不同長度的時候遇到需要處理的問題。各種語言在這個問題上是不同的。有些引起異常。有些在達到最短列表長度之後停止並忽略在其他列表上的額外項目。有些繼續直到最長列表長度,並對已經結束的列表,向函數傳遞某個占位符的值來指示沒有值。
Remove ads
參見
- 函子 (函數式編程)
- 卷繞 (計算機科學),也叫做「conv」或「zip」
- Filter (高階函數)
- Fold (高階函數)
- foreach循環
- 自由幺半群
- 高階函數
- 列表推導式
- Map (並行模式)
引用
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads