haskell學習筆記(7)-高階函數Curried functions

在很多語言中都有高階函數的存在,其定義也非常簡單,函數作爲參數和返回值傳來傳去,這樣的函數就被稱作高階函數。

例如很多gui庫都接受一個函數作爲事件的回調方法,這個就是函數作爲參數。
又比如java8的 Comparator.comparing,接受一個函數返回一個函數,也是高階函數。

函數式編程講究的就是一個靈活,我們可以把世間萬物看成行爲和數據,面向對象可以看成是對對象進行抽象,而函數式編程則是在很大程度上對行爲進行抽象。

例如java8的新加入的stream,或者很多語言裏list之類數據結構都有的幾個方法,filter,map,都是對行爲進行抽象,或者說對行爲進行組裝,它本身已經包含一部分行爲,然後需要傳入額外的行爲進行補全。例如filter本身含有的行爲是對每個元素進行判定,判定失敗的將被過濾,但是這個判定過程本身也是一個需要傳入的函數。

就像面向對象可以十分靈活的組織數據,函數式編程也可以非常靈活的組織行爲,而在haskell中這種靈活性更是被體現到了極致,haskell對高階函數提供了非常多的內建支持。


haskell裏定義的任意函數都支持不全調用,可以傳入不完整的參數來獲得另一個函數(這種行爲要在其他語言中實現只能是自己各種重載函數,基本上很難完成)。

《Haskell 趣學指南》裏這幾個例子可以說是非常好理解了。

--定義一個函數 把傳進來的三個NUM類型相乘
multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z
-- 只傳入一個參數進行不全調用,此時自動返回一個新的函數,這個函數取9和剩下的兩個參數乘了
let multTwoWithNine = multThree 9
--再對剛纔那個函數進行不全調用,傳入2,再返回一個新的函數,這個函數只取一個參數,然後乘上2*9
 let multWithEighteen = multTwoWithNine 2

不全調用按照我的理解,就是以任意不全的參數調用某函數時,會用實際值替換代數值,然後返回一個函數,那個函數只包括了剩下的代數值。

假如 java也有不全調用,那麼他應該是這個樣子

//常規的函數
public int max(int a,int b)
    {
        return a>b?a:b;
    }
//進行不全調用
max(100)
//此時應該返回這樣一個新函數
    public int max(int b)
    {
        return 100>b?100:b;
    }
//可以看到,就是一個用實際值替換代數值的過程

一些例子
取一數與 100 比較大小的函數

compareWithHundred :: (Num a,Ord a) => a -> Ordering
compareWithHundred = compare 100

一個檢查字符是否爲大寫的函數

isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])

不全調用就是用已有的函數快速生成一些適用面更窄的新函數的技術(例如max比較兩個數,以100不全調用max,返回一個比較某數與100的函數,他的適用面變窄了,因爲只能和100比較了)


高階函數只是一種概念,一種掌握後可以更靈活編碼的技術,在各種語言裏都可以實現,但haskell是內建了不全調用的,所以他的每個函數都可以看成是高階函數,而其他語言要實現一個高階函數就要費更多的事了。


觀察haskell的類型定義,例如max

--因爲haskell的類型定義是向左結合的,所以其實以下兩個聲明等價
 max :: (Ord a) => a -> a -> a
  max :: (Ord a) => a -> (a -> a)
  --可以看出, max 取一個參
--數 a,並返回一個函數 (就是那個 ->),這個函數取一個 a 類型的參數,返回一個 a。這便是爲何只用箭頭來分隔參數和返回值類型

《haskell趣學指南》裏的例子很好的展示了這一技術可以使代碼多簡潔

--想想看以下例子用命令式語言裏的循環需要寫多少代碼
ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]
[6,8,7,9]
ghci> zipWith' max [6,3,2,1] [7,3,1,5]
[7,3,2,5]
ghci> zipWith' (++) ["foo ","bar ","baz "] ["fighters","hoppers","aldrin"]
["foo fighters","bar hoppers","baz aldrin"]
ghci> zipWith' (*) (replicate 5 2) [1..]
[2,4,6,8,10]
ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]
[[3,4,6],[9,20,30],[10,12,12]]

haskell的lambda沒什麼特別的,就是和其他語言裏一樣的lambda(java8,kotlin,c#,js),語法都差不多

numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))

map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)]

haskell裏的foldl就是java8裏的reduce

sum' xs = foldl (\acc x -> acc + x) 0 xs

$就是一個改變函數結合順序的操作符,就是改變函數執行的優先級,一般是因爲默認的優先級有歧義。雖然括號也可以改變優先級,不過 用符號可以更明瞭(書上說的,我是沒感覺有什麼區別)

--下面兩個等價
 sum (map sqrt [1..130])
  sum $ map sqrt [1..130]

函數組合Function composition也是haskell內建的對高階函數的支持,也很好理解,就是高中數學裏的複合函數。

--f.g就是組合f函數和g函數,然後傳入參數的時候,就是先傳入g,然後結果再傳給f
--其他語言基本沒法實現這個特性,只能是自己手動嵌套
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

歡迎關注我的github
https://github.com/luckyCatMiao

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