減少繁瑣的括號嵌套
Haskell中的求值順序在1介紹。爲了明確表示求值順序,計算表達式中通常需要複雜的括號嵌套,比如((1+2)+(3*4)/(5/6))*(7+(8+9)-10)
。但是,繁瑣的括號嵌套有如下缺點:
- 會引起視覺負擔,在括號較爲繁瑣時並不能方便地看出表達式的求值順序。
- 繁瑣的括號嵌套也和數學直覺相悖。在數學中,
sin cos 1
代表的就是sin(cos(1))
的計算順序。
我們需要減少程序中的括號嵌套,增加計算式的可讀性。有兩種方法,用一個式子表示:f $ g $ h x = (f . g . h) x = f (g (h x))
,也就是使用函數應用符和函數複合符都可以減少括號的使用。但是這兩種東西的本質完全不同:分別介紹在下面。
函數應用符:($) and (&)
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
考慮到$
的最低優先級和右結合性,完全可以把$
視爲表達式的分割符:表達式被若干個$
分割成若干塊piece1 $ .. $ piecen
,在求值的時候首先求piecen
,然後繼續求值piece(n-1)
,在此期間apply掉最右側的$
,以此類推,求整個式子的值。
舉個例子:f (g1 h1 x) (g2 (h2 x y) z)
- 用
$
初步分塊成f (g1 h1 x) $ g2 (h2 x y) z
,表示先求g2 (h2 x y) z
再求f (g1 h1 x) ..
; - 然後繼續再不破壞計算順序的情況下把括號變成
$
,比如f (g1 h1 x) $ (g2 $ h2 x y) z
等等。
在用$
化簡計算式時,
infixl 1 &
(&) :: a -> (a -> b) -> b
x & f = f x
&
視爲對$
的一個補充,在使用方面的習慣和$
是完全一樣的,只是&
調換了兩個參數的順序。
既然如此,爲什麼要引入&
呢?&
用來在表達式中連續使用,來對參數連續應用函數:x & f1 & .. & fn = fn (.. (f1 x))
. 總體來說,$
的使用是爲了減少括號嵌套,而&
的使用場景是對標>>=
的,方便對參數連續應用函數。應用兩者的目的都是在編程時減少視覺負擔,所以兩者的引入都是必要的。
函數複合符:(.)
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
g . f = \x -> g (f x)
我們在使用(.)
的時候,只將其視爲一個普通的中綴函數。因爲其優先級最高,僅低於函數調用,所以在出現時,將其認爲是組合左邊已經求值的部分的中綴函數,功能是transform那部分值的類型,就可以理解。
(這樣說只是方便理解!實際上不是這樣的,.
作爲運算符,應用會比函數晚)。
比如(f . g . h) x
中,假設f :: c -> d, g :: b -> c, h :: a -> b
。由於右結合性,首先應用第二個.
,應用在g
和h
上得到g . h :: a -> c
。然後應用第一個.
,得到f . (g . h) :: a -> d
,最後應用在x
上,得到f . (g . h) x :: d
.
不難發現寫成f . g . h x
是錯誤的,因爲h x
會先求值,導致第二個.
的類型錯誤。