天天看點

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 
--再對剛才那個函數進行不全調用,傳入2,再傳回一個新的函數,這個函數隻取一個參數,然後乘上2*9
 let multWithEighteen = multTwoWithNine 
           

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

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

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

一些例子

取一數與 100 比較大小的函數

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

一個檢查字元是否為大寫的函數

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 [,,,] [,,,]
[,,,]
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' (*)) [[,,],[,,],[,,]] [[,,],[,,],[,,]]
[[,,],[,,],[,,]]
           

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

numLongChains = length (filter (\xs -> length xs > ) (map chain []))

map (\(a,b) -> a + b) [(,),(,),(,),(,),(,)]
           

haskell裡的foldl就是java8裡的reduce

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

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

函數組合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