Haskell:理解函數應用符和函數複合符,並應用($), (&), (.)減少嵌套調用中的括號嵌套

減少繁瑣的括號嵌套

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。由於右結合性,首先應用第二個.,應用在gh上得到g . h :: a -> c。然後應用第一個.,得到f . (g . h) :: a -> d,最後應用在x上,得到f . (g . h) x :: d.

不難發現寫成f . g . h x是錯誤的,因爲h x會先求值,導致第二個.的類型錯誤。

參考


  1. Haskell: 表達式的計算順序 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章