…高階函數
map :: (a->b) ->[a] ->[b],將函數f依次應用於序列[a],得到新的序列[b]。
filter :: (a->bool)->[a]->[a],利用函數f過濾序列[a]。
這兩個函數都可用list comprehension來實現,不過在某些情況下更簡潔。利用這兩個函數和CF組合可以生成各種需要的函數。
lambda
著名的lambda表達式(C++11中引入),也就是匿名函數,某些僅使用一次的函數不必命名,為了使代碼更簡潔,就有了lambda表達式。其格式為:
\ para1 para2->(return value)
\ (para1,para2) -> (return value)
由於Haskell有自動類型推斷,所以不必明確表明返回類型。注意這里直接return value了,也就無法使用模式匹配之類的。
折疊
折疊是對list處理模式的再抽象。所有遍歷list中元素並據此返回一個值的操作都可以交給fold實現。
foldl即為左折疊,從list左端開始。其參數分別是一個二元函數(參數分別是新值、累加值),一個初始值,一個待處理的list。
foldr為右折疊,由於模式匹配的時候一般不使用++,這時候就會用:配合foldr來完成任務。另外foldr適合處理無限長的list。
foldl1將參數中的初始值去掉,自動以list的head元素作為初始值;foldr1類似,取的是最后的元素。
scanl、scanr與fold系列類似,只是累加值的狀態會被記錄到下一次累加中(sum即scanl (+))。
$
$是函數調用符,與空格不同的是它的優先級最低,且為右結合,設計這個操作符的目的只是讓某些函數寫起來更簡單。比如 sqrt (1+2+3),可以簡單寫成sqrt$1+2+3
函數組合
函數組合對應着數學中的復合函數。如果想要生成新的函數,使用函數組合操作符"."可能會很方便。"."具有右結合性,右邊函數的返回值類型必須與左邊函數的參數值類型相符。它的作用類似於管道操作符,將先進行的運算結果傳給前面的函數。顯然函數組合只適用於只有一個參數的函數之間。一般也是用於簡潔化書寫。
Haskell是偏於數學的高等抽象語言,因此以上這些特性內置在語言中,只是給coders提供了一些更優雅的書寫方式,用前面學過的list comprehension都可以完成相同的功能,只是沒有那么優雅了╮(╯-╰)╭
第七章 模塊
前面六章主要介紹了函數,身為FP,函數顯然是最重要的組成部分。但是作為一門編程語言,一些必備的特性也會出現在Haskell中,例如:庫。
Haskell中的模塊是由一組相關的函數,類型和類型類組成的。程序的架構就是由主模塊調用其他模塊(類似C),前面介紹的函數都是裝載在prelude中的。
導入模塊的語法類似java,import xxx(在ghci中是:m xxx,可以一次導入多個),如果只是導入一部分函數,可以在模塊名后面在上(fun1,fun2..),如果要去除某一函數,可以在模塊名后面加上hiding (funx),后者主要是為了避免命名沖突。
避免命名沖突還有一個方法,就是加上限定符(類似C++),使用import qualified module_name as name1,關鍵字 qualified表面后面的模塊必須加上限定名,as則可以為限定符起別名。 下面介紹標准庫的常用模塊:
Data.List
顯然這里是前文介紹的所有list操作函數所在的庫。下面是一個簡表:
函數原型 |
解釋 |
intersperse :: a- > [a]- > [a] |
將第一個參數插入list相鄰的元素之間 |
intercalate :: [a] -> [[a]] -> [a] |
將第一個List交叉插入第二個List中間 |
transpose :: [[a]] -> [[a]] |
反轉一組List的List,類似轉置矩陣 |
foldl' & foldl1' |
非惰性版的foldl和foldl1,大list的時候用 |
concat :: [[a]] -> [a] |
合並list中的元素,可用來追加字符串 |
concatMap :: (a -> [b]) -> [a] -> [b] |
map一個list,然后concat |
and(or) :: [Bool] -> Bool |
對一組bool做與(或)運算 |
any(all) :: (a -> Bool) -> [a] -> Bool |
一組元素中滿足條件的元素存在(都是) |
iterate :: (a -> a) -> a -> [a] |
用參數2遞歸調用參數1,產生無限的list |
splitAt :: Int -> [a] -> ([a], [a]) |
在指定下標處切割list,返回一個tuple |
takeWhile :: (a -> Bool) -> [a] -> [a] |
將滿足條件的前面連續多個元素提取出來 |
dropWhile :: (a -> Bool) -> [a] -> [a] |
將前面不滿足條件的連續多個元素舍棄 |
span(break) :: (a -> Bool) -> [a] -> ([a], [a]) |
類似takeWhile,不過將結果切割了,分別從false處、true處斷開 |
sort :: Ord a => [a] -> [a] |
排序,以<順序 |
group :: Eq a => [a] -> [[a]] |
將相鄰並相等的元素分組,配合sort |
inits(tails) :: [a] -> [[a]] |
遞歸調用init(tail)直到什么都不剩 |
isInfixOf :: Eq a => [a] -> [a] -> Bool |
子序列判定,isPrefixOf、isSuffixOf用於判定首尾序列 |
elem、notElem |
判定是否子元素 |
partition :: (a -> Bool) -> [a] -> ([a], [a]) |
過濾元素並分組 |
find :: (a -> Bool) -> [a] -> Maybe a |
返回第一個滿足條件的元素 |
elemIndex :: Eq a => a -> [a] -> Maybe Int |
返回第一個相等元素的索引 |
elemIndices :: Eq a => a -> [a] -> [Int] |
返回所有滿足條件元素的索引集 |
findIndex :: (a -> Bool) -> [a] -> Maybe Int |
返回第一個滿足條件元素的索引 |
findIndices :: (a -> Bool) -> [a] -> [Int] |
返回所有滿足條件元素的索引集 |
zip,zipWith系列(<=7) |
將list對應元素組合成tuple的list |
lines :: String -> [String] |
將字符串按行分割 |
unlines :: [String] -> String |
line的反函數 |
words(unwords) |
合並單詞集或相反 |
nub :: Eq a => [a] -> [a] |
去除所有重復元素,組成新的list |
delete :: Eq a => a -> [a] -> [a] |
刪除第一個與之相等的元素 |
union(intersection) :: Eq a => [a] -> [a] -> [a] |
返回兩個list的並(交)集,以第一個為主順序 |
insert :: Ord a => a -> [a] -> [a] |
保持順序的插入元素 |
以上函數中,參數為int的都有更通用的版,在前面加上genernic就可得到通用版(如genericLength);
而用(==)判定相等的函數,可以改成用謂詞作判定依據,只需要在其后加上By,如sortBy,groupBy等。
這里引入了Data.Function中的on函數:on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
f `on` g = \x y -> f (g x) (g y),這個函數可以使By系列的函數使用起來更加簡潔。判斷相等性常用 (==) `on` xxx,判定大小則常用 compare `on` xxx.
Maybe類型是在可能返回nothing時使用,這樣返回時如果存在就用Just xxx的格式,否則返回Nothing
Data.Char
這個模塊主要包含了一些字符處理函數。類似於C中的<ctype.h>
函數原型 |
解釋 |
isControl :: Char -> Bool |
判斷控制字符 |
isSpace |
判斷空格字符(space、tab、\n) |
isLower |
判斷小寫字符 |
isUpper |
判斷大寫字符 |
isAlpha(isLetter) |
判斷字母 |
isAlphaNum |
判斷字符或數字 |
isPrint |
判斷可打印字符 |
isDigit(isNumber) |
判斷數字 |
isOctDigit |
判斷八進制數字 |
isHexDigit |
判斷十六進制數字 |
isMark |
判斷Unicode注音(for Frenchman) |
isPunctuation |
判斷標點符號 |
isSymbol |
判斷貨幣符號 |
isSeperater |
判斷Unicode空格或分隔符 |
isAscii |
判斷unicode字符表前128位 |
isLatin1 |
判斷unicode字符表前256位 |
isAsciiUpper |
判斷大寫ascii字符 |
isAsciiLower |
判斷小寫ascii字符 |
generalCategory :: Char -> GeneralCategory |
判斷所在分類 |
toUpper :: Char -> Char |
小寫轉換成大寫,其余不變 |
toLower |
與上面相反 |
toTitle |
將一個字符轉換成title-case |
digitToInt(ord) |
字符轉換成int值 |
intToDigit(char) |
int轉換成字符 |
注意這里的函數比C中的字符處理函數要多得多,除了考慮unicode因素外,這里將各種進制的數看做完全不同的東西。也就是說,並不依據計算機內存模型。
Data.Map
這里的map類似C++中的關聯容器std::map.一個list里面全是pair(兩位的tuple)。內部構造是由紅黑樹完成的,鍵值必須可排序。
函數原型 |
解釋 |
fromList :: Ord k => [(k, a)] -> Map k a |
將一個關聯列表轉換成一個map |
insert :: Ord k => k -> a -> Map k a -> Map k a |
插入一對鍵值 |
empty :: Map k a |
返回一個空map |
null :: Map k a -> Bool |
測試一個map是否非空 |
size :: Map k a -> Int |
返回map大小 |
singleton :: k -> a -> Map k a |
由參數構建一個pair( |
lookup :: Ord k => k -> Map k a -> Maybe a |
查找鍵,找到后返回對應的值 |
member :: Ord k => k -> Map k a -> Bool |
查找鍵是否存在 |
map、filter |
類似list中的函數,只針對value(與key無關) |
keys、elems |
返回由key或者value組成的list |
toList :: Map k a -> [(k, a)] |
由map生成對應的list |
fromListWith :: Ord k => (a -> a -> a) -> [(k, a)] -> Map k a |
類似於multimap,可以由一個key對應多個鍵值,參數1選擇key值相同的鍵如何處理 |
insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a |
insert的復數版,如果存在相同key,由參數1決定如何處理value |
Data.Map里面還有不少函數,常見的操作已經都列舉在上面了。
Data.Set
與STL::set類似的數據結構,鍵值唯一。
函數原型 |
解釋 |
fromList :: Ord a => [a] -> Set a |
與map中的類似,將list轉化為set |
difference :: Ord a => Set a -> Set a -> Set a |
返回第一個有第二個沒有的元素集 |
union :: Ord a => Set a -> Set a -> Set a |
取並集 |
null,size,member,empty,singleton,insert,delete |
與map的接口功能類似 |
isSubsetOf :: Ord a => Set a -> Set a -> Bool |
判斷是否子集 |
isProperSubsetOf :: Ord a => Set a -> Set a -> Bool |
判斷是否真子集 |
map和filter |
也類似於map |
這里還討論了Data.List.nub的執行效率問題,nub要求List元素具有Eq類型類,而要使用集合來去除重復元素,必須是Ord類型類。后者的執行速度比前者,前者在大list時不如setNub(但是setNub不穩定)。
自定義模塊
自定義模塊的模塊名與文件名必須一致(這點類似matlab中的.m文件)。格式是:
module Xxx
(fun1,fun2..)
where fun1 :: a->b->c
fun1=…
fun2::…
注意模塊名首字母大寫。如果要采用分層的方法,則新建一個文件夾,為模塊名(如Data),然后建文件(如List),導入時把整個文件夾放在引用它的文件的同一目錄下。注意聲明的時候前面要加上限定符(如module Data.List)。
雖然module看起來很類似C中間的Struct,但是還是有本質的不同,最明顯的就是它只封裝了一系列的函數,而C中間則只能存放變量。另外就是Haskell並不支持面向對象。