43

我看到很多功能是根據模式(f .) . g定義的。例如:什麼是(f。)。 g的意思是在Haskell中?

countWhere = (length .) . filter 
duplicate = (concat .) . replicate 
concatMap = (concat .) . map 

這是什麼意思?

+4

(F)。g也可能是原作者代碼中很好僞裝的觀點的一部分。 – Marton

+1

我不確定這意味着什麼。 –

+0

這意味着作者正在聰明,並最終編寫不可讀的代碼。 ;) – tibbe

回答

82

點運算符(即(.))是function composition運算符。它被定義爲如下:

infixr 9 . 
(.) :: (b -> c) -> (a -> b) -> a -> c 
f . g = \x -> f (g x) 

正如你可以看到它需要b -> c類型的函數,並且a -> b類型的另一個函數,並返回a -> c類型的函數(即,所述第二函數的結果適用於第一功能)。

函數組合運算符非常有用。它允許您將一個函數的輸出傳遞給另一個函數的輸入。

main = interact (\x -> unlines (reverse (lines x))) 

不是很易讀:舉例如下,你可以寫在Haskell一個tac程序。使用功能組成但是你如下可以寫它:

main = interact (unlines . reverse . lines) 

正如你所看到的函數組合是非常有用的,但你不能在任何地方使用它。例如,你不能管的filter輸出到length使用函數組合:

countWhere = length . filter -- this is not allowed 

這是不允許的原因是因爲filter(a -> Bool) -> [a] -> [a]類型。將其與a -> b進行比較,我們發現a的類型爲(a -> Bool),而b的類型爲[a] -> [a]。這導致類型不匹配,因爲Haskell預計length的類型爲b -> c(即([a] -> [a]) -> c)。但它實際上是[a] -> Int類型。

的解決方案是非常簡單的:

countWhere f = length . filter f 

但是有些人不喜歡額外的懸掛f。他們喜歡寫countWherepointfree風格如下:

countWhere = (length .) . filter 

他們如何獲得呢?試想一下:

countWhere f xs = length (filter f xs) 

-- But `f x y` is `(f x) y`. Hence: 

countWhere f xs = length ((filter f) xs) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere f = length . (filter f) 

-- But `f . g` is `(f .) g`. Hence: 

countWhere f = (length .) (filter f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere = (length .) . filter 

正如你可以看到(f .) . g簡直是\x y -> f (g x y)。這個概念實際上可以重複:

f . g    --> \x -> f (g x) 
(f .) . g   --> \x y -> f (g x y) 
((f .) .) . g  --> \x y z -> f (g x y z) 
(((f .) .) .) . g --> \w x y z -> f (g w x y z) 

這不是很漂亮,但它完成了工作。鑑於兩個功能,你還可以編寫自己的函數組合運營商:

f .: g = (f .) . g 
f .:: g = ((f .) .) . g 
f .::: g = (((f .) .) .) . g 

使用(.:)操作符,你可以寫countWhere爲代替如下:有趣的是

countWhere = length .: filter 

儘管你可以在點自由風格寫(.:)好:

f .: g = (f .) . g 

-- But `f . g` is `(.) f g`. Hence: 

f .: g = (.) (f .) g 

-- But `\x -> f x` is `f`. Hence: 

(f .:) = (.) (f .) 

-- But `(f .)` is `((.) f)`. Hence: 

(f .:) = (.) ((.) f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

(.:) = (.) . (.) 

同樣,我們得到:

(.::) = (.) . (.) . (.) 
(.:::) = (.) . (.) . (.) . (.) 

正如你可以看到(.:)(.::)(.:::)都只是(.)權力(即他們是的(.))。對於數字數學:

x^0 = 1 
x^n = x * x^(n - 1) 

同樣的功能在數學:

f .^ 0 = id 
f .^ n = f . (f .^ (n - 1)) 

如果f(.)則:

(.) .^ 1 = (.) 
(.) .^ 2 = (.:) 
(.) .^ 3 = (.::) 
(.) .^ 4 = (.:::) 

這使我們接近這個文章的末尾。對於最後的挑戰,讓我們寫pointfree風格以下功能:

mf a b c = filter a (map b c) 

mf a b c = filter a ((map b) c) 

mf a b = filter a . (map b) 

mf a b = (filter a .) (map b) 

mf a = (filter a .) . map 

mf a = (. map) (filter a .) 

mf a = (. map) ((filter a) .) 

mf a = (. map) ((.) (filter a)) 

mf a = ((. map) . (.)) (filter a) 

mf = ((. map) . (.)) . filter 

mf = (. map) . (.) . filter 

我們可以進一步簡化這個如下:

compose f g = (. f) . (.) . g 

compose f g = ((. f) . (.)) . g 

compose f g = (.) ((. f) . (.)) g 

compose f = (.) ((. f) . (.)) 

compose f = (.) ((. (.)) (. f)) 

compose f = ((.) . (. (.))) (. f) 

compose f = ((.) . (. (.))) (flip (.) f) 

compose f = ((.) . (. (.))) ((flip (.)) f) 

compose = ((.) . (. (.))) . (flip (.)) 

使用compose你現在可以寫mf爲:

mf = compose map filter 

是的,它有點醜,但它也是一個非常令人敬畏的令人難以置信的概念。您現在可以編寫\x y z -> f x (g y z)的任何函數作爲compose f g,這非常整潔。

+2

表達式'(。)^ i'不是很好的類型,所以它們實際上並不是有效的Haskell。 –

+1

是的。然而,我確實寫了「對數學中的函數類似」,因爲這是一個數學解釋,我認爲使用'^'代替數字函數是可以的。不過,我會將操作符更改爲'。^'來區分這兩者。 –

+0

我也很驚訝地發現數學中還有(。)^ i'。也許這種事物的正式框架存在於依賴型理論中。這將是有趣的。 –

11

這是一個味道的問題,但我覺得這樣的風格是不愉快的。首先我會描述它的意思,然後我建議一個我更喜歡的替代方案。

對於任何運營商?,您需要知道(f . g) x = f (g x)(f ?) x = f ? x。由此我們可以推斷,

countWhere p = ((length .) . filter) p 
       = (length .) (filter p) 
       = length . filter p 

所以

countWhere p xs = length (filter p xs) 

我更喜歡使用一個函數調用.:

(.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z 
(f .: g) x y = f (g x y) 

然後countWhere = length .: filter。我個人覺得這更清晰。

.:Data.Composition,可能其他地方也定義)

+0

您也可以將'(。:)'定義爲'(。:) = fmap fmap fmap'。它更通用,因爲你可以將它用於任何函子。例如你可以做'(* 2)。:只是[1..5]'。當然,你需要給它正確的類型簽名'(。:) :) ::(Functor f,Functor g)=>(a - > b) - > f(g a) - > f(g b)'。 –

+6

@AaditMShah在這種情況下,我寧願像'<$$> = fmap。因爲'(。:)'按照慣例專門用於'( - >)r',而「外部」'fmap'則在'( - >)r'仿函數上。 – kqr