工具
haskell platform虏冻,直接百度安裝.
打開控制臺輸入ghci即進(jìn)入交互模式况鸣。
假如定義了myfunction.hs
,在ghci中輸入:l myfunction.hs
便會進(jìn)行加載∏┰撸‘
細(xì)節(jié)
- 函數(shù)的優(yōu)先級比運(yùn)算高,如
succ 2*3
會先計算succ 2
。 - 非運(yùn)算使用
not
- 真值用
True
绢馍,假值用False
,注意開頭大寫肠套。 -
/=
表示不等 - 取余用的是
mod
- 單行注釋使用
--
舰涌,多行注釋使用{- -}
初學(xué)者第一個函數(shù)
doubleMe x = x * x
.
創(chuàng)建test.hs,鍵入以上函數(shù)你稚,加載方式為::l test.hs
瓷耙,之后就可使用。
也可以在test.hs鍵入兩行函數(shù):
doubleMe x y = x*y + x*y
doubleUs x y = doubleMe x y + doubleMe x y
之后再重新加載刁赖,兩個函數(shù)都可以使用搁痛。
在haskell中if then else
是一種表達(dá)式。如
doubleSmallNumber x = if x>100 then x else x*2
可以看到宇弛,then esle是不可省略的鸡典,必須有一個確定的最終值。
首字母大寫的函數(shù)是不允許的枪芒。
類似下面的沒有參數(shù)的函數(shù)彻况,其實(shí)就是定義了一個常量字符串:
someName = "hahahhahahahahahah..."
list
在ghci下使用let
定義一個常量
let a = 1
- 字符串"aaaa"其實(shí)就是list的語法糖=>
['a','a','a','a']
- list中的所有的元素的數(shù)據(jù)類型必須相同。
- list中是通過
++
進(jìn)行合并操作舅踪。[1,2,3] ++ [4,5,6]
.但注意使用++
進(jìn)行合并字符串的時候纽甘,其會遍歷++
左邊的字符串,如果左邊字符串較長抽碌,會浪費(fèi)很長時間贷腕,這個時候,可以使用:
運(yùn)算符,表示插入操作泽裳,如:1 : [2,3,4,5]
-
[1,2,3]
實(shí)際是1:2:3:[]
的語法糖瞒斩,[]
表示空list - 按照索引取list中的元素,可以使用
!!
,如:"im ok"!!0
會打印出i字符 - list可以比較大小涮总,但是會從前往后依次比較胸囱,直到遇到不等的關(guān)系
-
head
取list頭,tail
取除head外的數(shù)據(jù)瀑梗,last
去list的尾烹笔,init
取除last外的數(shù)據(jù) -
length
返回list的長度,null
檢查list是否為空抛丽,reverse
將一個list反轉(zhuǎn)谤职。take
返回一個list的前幾個元素,如:take 3 [1,2,3,4,5]
,drop
與take用法大體相同,會刪除list的前幾個元素亿鲜。maximum
返回list中最大的元素允蜈,minimum
返回最小,sum
,elem
判斷l(xiāng)ist中是否有某個元素蒿柳,使用中綴形式饶套。
range
-
[1..20]
表示從1到20的list -
['a'..'z']
表示從a到z的list -
[1,3..20]
表示[1,3,5,7,9,11,13,15,17,19] - 不推薦range使用浮點(diǎn)數(shù)
-
take 24 ([2,4..])
可以獲取24個2往后的偶數(shù),和[2,4..2*24]
是一樣的垒探,但是前者好點(diǎn) -
take 10 (cycle [1,2,3])
,cycle表示某個列表的循環(huán) -
take 10 (repeat 5)
妓蛮,repeat表示某個元素的循環(huán)。另一種簡便方法是replicate 3 10
=> [10,10,10]
list comprehension
定義集合的操作
[x*2 | x <- [1..10]]
- 有條件的集合:
[x*2 | x<-[1..10],x*2 >= 12]
,逗號隔開 - 取50到100間除7余3的數(shù):
[x | x<-[50..100],x`mod`7 == 3]
- 偶數(shù)轉(zhuǎn)換為even,基數(shù)為odd:
[if x `mod` 2 == 0 then "even" else "odd" | x <- [1..10]]
- 多個限制條件:
[ x | x <- [10..20], x /= 13, x /= 15, x /= 19]
- 多個元素:
[ x*y | x <- [2,5,10], y <- [8,10,11]]
-
length' xs = sum [1 | _ <- xs]
表示獲取xs列表的長度圾叼,其中_
表示不關(guān)心當(dāng)前值蛤克。 - 嵌套list:
let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
=>[ [ x | x <- xs, even x ] | xs <- xxs]
tuple
tuple是元組。
- list中的元素數(shù)據(jù)類型必須相同夷蚊,但是tuple中的元素數(shù)據(jù)類型不必相同
-
[(1,2),(8,11,5),(4,5)]
會報錯咖耘,因?yàn)?code>(1,2)和(8,11,5)
不是相同類型。而(1,2)
和(4,5)
是相同類型 - tuple可以用來表示某個人的一系列信息撬码,如
("zhangsan","henan",19)
- tuple中的項(xiàng)的數(shù)目是確定的,不允許追加
-
fst
取tuple(二元組)的首元素版保,snd
取tuple(二元組)的尾元素 - zip函數(shù)將兩個交叉list生成tuple形式的list:
zip [1,2,3,4,5] [5,5,5,5,5]
=>[(1,5),(2,5),(3,5),(4,5),(5,5)]
type
- 在ghci中使用
:t
來獲取任何表達(dá)式的類型.如:t 'a'
輸出'a'::Char - 凡是明確的類型呜笑,其首字母必須為大寫的字母,所以一般對于函數(shù)來說彻犁,首字母不能大寫叫胁。
- 函數(shù)也有類型,定義函數(shù)的時候汞幢,加上參數(shù)的類型和輸出類型是好習(xí)慣驼鹅,如:
removeNonUppercase :: [Char]->[Char]
表示輸入的是字符串,輸出還是字符串。其中[Char]
和String
是等價的输钩。如果是多個參數(shù)豺型,則使用以下形式:addThree :: Int -> Int -> Int -> Int
表示輸入三個整形,輸出1個整形买乃。 - Integer也表示整數(shù)姻氨,但是是無界的,所以可以表示大數(shù)
- 某些函數(shù)定義的時候傳入的不是參數(shù)剪验,而是
a
,b
這種肴焊,這些表示類型參數(shù),表示可以傳入任何類型功戚。如:t head
=>head::[a] -> a
-
:t (==)
可以查看==
的類型娶眷,輸出為(==) :: Eq a => a -> a -> Bool
,=>
為類型約束,表示a這種類型應(yīng)該為Eq類型啸臀,即相同類型届宠。
typeclass
(一)
- 可以把typeclass想像為java中的interface
-
Eq表示可判斷相等性的類型,除函數(shù)以外所有類型都屬于
Eq
- Ord可包含比較大小的類型
-
Show除函數(shù)以外所有類型都是show類型壳咕,
show
函數(shù)可以取任意Show類型轉(zhuǎn)換為字符串席揽。 -
Read
read
函數(shù)讀取字符串轉(zhuǎn)換為某Read成員類型。如read "5" + 5
,read "5"::Int
這種方式可以指定轉(zhuǎn)換的類型谓厘,若不知道類型的情況下幌羞。其中::Int
表示類型注釋,明確前邊的類型竟稳。 - Enum表示可枚舉属桦,好處是可以使用succ,pred等函數(shù)來取得上一個,下一個他爸。
-
Bounded表示成員有上下界聂宾。如
minBound :: Int
=>-2147483648
- Num所有數(shù)字類型
-
Integral所有整數(shù)類型,如
Int
,Integer
即為Integral類型 -
Floating 同上诊笤。
Float
和Double
為該類型
(二) -
class
關(guān)鍵字可以創(chuàng)造一個typeclass
函數(shù)
succ 6
輸出7 表示某個值的后繼
min 4 5
,max 4 5
.
模式匹配
其實(shí)就是類似switch
如下代碼:
lucky::(Integral a)=> a -> String
lucky 7 = "its 7"
lucky x = "its not 7"
如果匹配到7系谐,則后續(xù)不執(zhí)行。如果未匹配到讨跟,則所傳參數(shù)綁定到x上纪他。但是如果是下面的代碼:
lucky::(Integral a)=> a -> String
lucky x = "its not 7"
lucky 7 = "its 7"
則報錯,因lucky 7 已經(jīng)被加載晾匠,lucky x已經(jīng)包含了lucky 7茶袒。
可以通過這種方式實(shí)現(xiàn)遞歸:
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
_
符號表示不關(guān)心值,如:
first :: (a,b,c) -> a
first (x,_,_) = x
可以通過:
來匹配List,因?yàn)?code>[a,b,c]本來就是a:b:c:[]
的語法糖
如a:b
會將[1,2,3]
匹配成1:[2,3]
凉馆,而如果匹配單元素List薪寓,可以寫為(x:[])
亡资,匹配雙元素List:(x:y:[])
,也可以不加括號向叉,寫為[x]
或[x,y]
锥腻,但是(x:y:_)
必須加括號,注意此處加上括號并不是表示tuple.因?yàn)?[1,2,3])還是一個數(shù)組植康,單元素的tuple其實(shí)毫無意義旷太。
實(shí)現(xiàn)head:
head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x
error是一個函數(shù),會導(dǎo)致程序崩潰销睁。
實(shí)現(xiàn)length:
length' :: (Num b) => [a] -> b
length' [] = 0
length' (_:xs) = 1 + length' xs
xs@(x:y:ys)
類似這種形式的模式供璧,xs就表示整體,如:
capital :: String -> String
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
guard
類似if語句冻记,模式匹配是匹配值睡毒,而guard則匹配bool
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
與模式匹配不同的是,guard模式是通過判斷表達(dá)式的真假來運(yùn)作的冗栗。直到遇到一個為真的表達(dá)式演顾,并且|
必須與前邊有縮進(jìn)。
在定義函數(shù)的時候如func a b
也可以這么定義a `func` b
guard也可以和模式匹配進(jìn)行配合隅居,如果guard沒有匹配到結(jié)果钠至,后續(xù)沒有代碼則報錯胎源,但是如果后續(xù)還有模式匹配的代碼則繼續(xù)執(zhí)行棉钧,比如實(shí)現(xiàn)take:
myTake :: (Num b, Ord b) => [a] -> b -> [a]
myTake _ b
| b <= 0 = []
myTake [] _ = []
myTake (x:xs) b = x : myTake xs (b-1)
guard后邊跟著模式匹配,代碼不會報錯涕蚤。
where
在guard模式中宪卿,可以通過where來引用某個復(fù)雜的變量值,這樣就不用重復(fù)出現(xiàn)某個復(fù)雜的表達(dá)式了万栅。如:
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
where也支持模式匹配佑钾,如:
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)
所以下面的代碼不難理解:
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
where也可以定義函數(shù):
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
let
格式:let [bindings] in [expressions]
let in是一個表達(dá)式,其值是expressions表示的值烦粒,bindings中進(jìn)行局部變量的定義休溶。與where
不同的是,let in
是一個表達(dá)式扰她,所以可以隨處安放兽掰,同if else then
,而where是一個語法結(jié)構(gòu)义黎,一般只用在guard后綴。
如
maax x = let y = 1 in y
let也可以定義局部函數(shù):
[let square x = x * x in (square 5, square 3, square 2)]
定義多個名字豁跑,使用;
隔開
(let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)
使用模式匹配:
(let (a,b,c) = (1,2,3) in a+b+c) * 100
用在list中:
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
(w, h) <- xs 這里無法使用 bmi 這名字廉涕,因?yàn)樗?let 綁定的前面泻云。
case
case 是一個表達(dá)式,與switch相似:
格式為:
case expression of pattern -> result
pattern -> result
pattern -> result
如:
head xs = case xs of [] -> error "error"
(x:_) -> x
模式匹配本質(zhì)上就是case的語法糖。
上述代碼寫成模式匹配則為:
head [] = error "error"
head (x:_) = x
遞歸
haskell中實(shí)現(xiàn)while和for的方案就是遞歸狐蜕。
實(shí)現(xiàn)取列表中最大值:
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
遞歸實(shí)現(xiàn)的快排宠纯,真是特妹的優(yōu)雅!!
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
let smallerSorted = quicksort [a | a <- xs, a <= x]
biggerSorted = quicksort [a | a <- xs, a > x]
in smallerSorted ++ [x] ++ biggerSorted
如實(shí)現(xiàn)的reverse函數(shù):
myReverse :: [a] -> [a]
myReverse [] = []
myReverse (x:xs) = myReverse xs ++ [x]
一定要注意,最后一行為什么不寫成:myReverse xs : x
呢层释,原因是沒有[1,2,3]:3
這種寫法婆瓜,但是有[1,2,3] ++ [3]
這種寫法或者1:[1,2,3]
遞歸的固定模式可以描述成這樣:先定義一個邊界條件,再定義函數(shù)贡羔,讓它從一堆元素中取一個并做點(diǎn)事情后廉白,把剩余的元素重新交給該函數(shù)。
高端函數(shù)
指可以接受函數(shù)作為參數(shù)乖寒,也可以返回函數(shù)作為結(jié)果猴蹂。
curried functions
原則上haskell的所有函數(shù)都只有一個參數(shù),定義的函數(shù)傳多個參數(shù)是怎么來的楣嘁?
所有多個參數(shù)的函數(shù)都是curried function磅轻,如func a b
傳入兩個參數(shù),實(shí)際上是func a
回傳了一個函數(shù)逐虚,并將b傳給該函數(shù)聋溜。
如:max::(Ord a)=> a->a->a
可以看作max::(Ord a) => a -> (a -> a)
max a
表示返回一個a->a
類型的函數(shù)。那么max a b
可以理解為 (max a) b
所以如果想要構(gòu)造一個和7比較大小的函數(shù)叭爱,直接調(diào)用max 7
即可,因?yàn)閙ax 7會返回一個(a->a)的函數(shù)撮躁。 如下代碼:
max7 :: (Ord a, Num a) => a -> a
max7 = max 7
所以此時max7為(a->a)的函數(shù)。
查看以下代碼:
ghci> let multWithEighteen = multTwoWithNine 2
ghci> multWithEighteen 10
180
以上代碼可以看出涤伐,一個參數(shù)沒有傳入全的函數(shù)會返回另一個函數(shù)馒胆,等待剩余的參數(shù)傳遞完畢。
中級函數(shù)也可以返回函數(shù):
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)
這個例子就可知道形如(/10)
(+3)
(++ "abc") (3:) (3+)
都是函數(shù)凝果。
同樣的(*) (+) (++)
也都是函數(shù)祝迂,不過這樣函數(shù)的參數(shù)為兩個。
(/10) 200
和200/10
是等價的器净。而(200/) 10
和200/10
也是等價的型雳。
以下代碼調(diào)用某個函數(shù)兩次:
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
從代碼中可以看出 第一個參數(shù)是(a->a)類型,也就是該類型的函數(shù)山害,在這括號是必須的纠俭,表示第一參數(shù)必須是一個函數(shù),第二個參數(shù)可以是任意元素浪慌,最后一個參數(shù)返回某個元素冤荆。
那么調(diào)用:applyTwice (+3) 10
其實(shí)就是((+3) ((+3) 10))
map和filter
map:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
map是結(jié)合高端函數(shù)和遞歸來實(shí)現(xiàn)的。
filter:
filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
lambda
匿名函數(shù)权纤,樣式是\ 參數(shù) -> 函數(shù)體
钓简,通常會用括號將lambda函數(shù)括起來乌妒,否則會引起歧義。
如\x -> x + 3
表示輸入x輸出x+3外邓。同樣lambda可以取多個參數(shù):\a b -> a + b
撤蚊。再看個復(fù)雜點(diǎn)的例子:
addThree :: (Num a) => a -> a -> a -> a
addThree = \x -> \y -> \z -> x + y + z
這種樣式也是可以。
其他高級函數(shù)
foldl
其實(shí)就是Java stream中的reduce
foldl
是折疊损话,將一個數(shù)組從前折到后侦啸,傳入的第一個參數(shù)是函數(shù),第二個參數(shù)是初始值丧枪,第三個參數(shù)為List,
foldl
是fold left, 而foldr
則是fold right,即從右邊開始折疊光涂。
foldl1
和foldr1
則與foldl
和foldr
相似,不過他們的初始值為數(shù)組的第一個元素(首個或末尾)豪诲,只不過計算空List則會報錯顶捷。
foldl (\x y -> x+y) 0 [1,2,3]
foldl (+) 0 [1,2,3]
let sum = foldl (+) 0 [1,2,3] in sum [1,2,3]
因?yàn)閒old函數(shù)的特殊性,傳入List傳入Item屎篱,所以可以用來實(shí)現(xiàn)一些遍歷的庫函數(shù)服赎,如max,min等。只要滿足返回的結(jié)果不為List交播,都可以想辦法完成重虑。
scan
函數(shù)與fold
函數(shù)不同的是,scan
會將每步的計算結(jié)果保存在List中秦士,如:
scanl (+) 0 [1,2,3,4] 輸出:[0,1,3,6]
其同樣有scanl
,scanr
, scanl1
,scanr1
等函數(shù)
$操作符
$
操作符的優(yōu)先級最低缺厉,所以其可以充當(dāng)(),如sqrt 3 + 4 + 5
表示根3 + 4 + 5的值隧土,如果我想取sqrt(3 + 4 +5)
那么也可以寫成sqrt $ 3+4+5
其首先會計算符號來簡化代碼:sum (map sqrt [1..130])
可以寫成sum $ map sqrt [1..130]
函數(shù)組合:
數(shù)學(xué)中的函數(shù)組合為:
在haskell中可以使用.
號來表示辐脖,如:map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]
表示將所有的x先獲取絕對值,然后通過negate取負(fù)皆愉,那么使用函數(shù)組合則表示為:map (negate . abs) [5,-3,-6,7,-3,2,-19,24]
,這樣表示的函數(shù)更為方便嗜价,但是注意函數(shù)組合的順序。
函數(shù)組合可以構(gòu)造更多的函數(shù)幕庐,形如f1 . f2 a
的函數(shù)實(shí)際上和f1(f2 a)
等價的久锥。
模塊
類比Java中的類,裝在模塊的方式是通過import
在某個.hs文件中使用import Data.List
可以將Data.List模塊裝入异剥,這個模塊有很多操作List的方法瑟由。
如果使用ghci交互界面來裝載模塊,可以使用:m Data.List
,使用:m Data.List Data.Map Data.Set
裝載多個函數(shù)冤寿。其實(shí)ghci初始的時候會裝載Prelude
模塊歹苦,所以你能使用到filter
绿鸣,map
等常用函數(shù)
import Data.List (nub,sort)
只裝載nub和sort函數(shù)
import Data.List hiding (nub)
除了nub函數(shù),其他都裝載
import qualified Data.Map
關(guān)鍵字qualified
表示暂氯,如果調(diào)用該模塊中的某個與外部函數(shù)同名的函數(shù),就必須加上Data.Map前綴亮蛔。
import qualified Data.Map as M
同名函數(shù)加M前綴即可:M.filter
Data.List
Data.List有很多方便處理List的函數(shù)痴施,如map,filter
等,為了方便究流,將Data.List中的一些函數(shù)直接加入到haskell中辣吃,所以調(diào)用的時候,就不用再寫Data.List前綴芬探。
以幾個罕見函數(shù)舉例:
-
intersperse '.' "money"
:類似java中的join函數(shù)神得,將第一個參數(shù).
加到list中每兩個元素的中間 -
intercalate " " ["I","Love","you"]
:跟上邊的 函數(shù)類似,不過將第一個參數(shù)換成了list -
transpose [[1,2,3],[4,5,6],[7,8,9]]
= > [[1,4,7],[2,5,8],[3,6,9]] ,不做解釋了偷仿,心累哩簿。 -
concat
將一組list鏈接為一個list。concat ["123","456"]
輸出"123456"
-
and
and取一組Bool的list酝静,只有所有的元素都為True才會返回True节榜,否則返回False.and $ map (>4) [5,6,7,8]
-
or
與and
類似,不過是or邏輯 -
any
和all
表示取list判斷是否有一個符合或者都符合如:any (== 4) [2,3,4,5,6]
返回True,all (>4) [5,6,7,8]
返回True. -
splitAt
分割list别智,在指定位置斷開splitAt 3 "heyman"
返回("hey","man") -
takeWhile
從list中取元素宗苍,一旦遇到不符合條件的元素就停止:takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]
返回[6,5,4] -
dropWhile
與takeWhile
相似,不過符合條件就刪掉薄榛,直到遇到不符合條件的讳窟。dropWhile (/=' ') "This is a sentence"
返回" is a sentence"
-
span
與takeWhile
相似,不過其返回兩個list,第一個list是takeWhile返回的list辰晕,第二個list是剩余的list. -
sort
排序一個list -
group
取一個list作參數(shù)刃永,將其中相鄰并相等的元素各自歸類。 -
isPrefixOf
與isSuffixOf
檢查一個List是否以另一個List開頭或結(jié)尾碌上。 -
partition
取一個限制條件和List作為參數(shù),返回兩個List,第一個List包含所有符合條件的元素浦徊,第二個List包含剩余的元素馏予。 -
find
接受一個函數(shù)和List,返回第一個符合條件的元素盔性,這個元素是個Maybe值霞丧。返回如下面的形式Just 5
、Nothing
-
findIndex
與find
相似冕香,不過返回的是Maybe的索引蛹尝。 -
elemIndex
與elem
相似后豫,返回的是Maybe的索引。 -
lines
傳入一個字符串突那,將字符串分行:lines "first line\nsecond line\nthird line"
返回["first line","second line","third line"]
-
unlines
是lines
的反函數(shù) -
words
和unwords
可以把一個字符串分成一組單詞或運(yùn)行相反的操作挫酿。 -
nub
將一個List中的重復(fù)元素全部篩掉。 -
delete
刪除List中第一個出現(xiàn)的某元素:delete 'h' "hey there"
返回 "ey there"愕难,delete 'h'. delete 'h' "hey there"
返回"ey tere" -
\\
差集早龟,左集扣除右集合后的集合 -
union
并集 -
intersection
交集 -
insert
將某個元素插入的可排序的List中,插入到首個大于等于該值的前邊
有個函數(shù)叫on
函數(shù)猫缭,其定義如下:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
f `on` g \x y -> f (g x) (g y)
即把g函數(shù)的結(jié)果傳遞給f函數(shù)葱弟,所以(==) `on` (>0)
就表示函數(shù)\x y -> x>0 == y >0
,同理 compare `on` length
就表示\x y -> length x `compare` length y
Data.Char
處理字符串的模塊
-
isControl
判斷一個字符是否是控制字符。 -
isSpace
判斷一個字符是否是空格字符猜丹,包括空格芝加,tab,換行符等. - -
isLower
判斷一個字符是否為小寫. -
isUper
判斷一個字符是否為大寫射窒。 -
isAlpha
判斷一個字符是否為字母. -
isAlphaNum
判斷一個字符是否為字母或數(shù)字. -
isPrint
判斷一個字符是否是可打印的. -
isDigit
判斷一個字符是否為數(shù)字. -
isOctDigit
判斷一個字符是否為八進(jìn)制數(shù)字. -
isHexDigit
判斷一個字符是否為十六進(jìn)制數(shù)字. -
isLetter
判斷一個字符是否為字母. -
isMark
判斷是否為 unicode注音字符藏杖,你如果是法國人就會經(jīng)常用到的. - isNumber 判斷一個字符是否為數(shù)字. -
isPunctuation
判斷一個字符是否為標(biāo)點(diǎn)符號. -
isSymbol
判斷一個字符是否為貨幣符號. -
isSeperater
判斷一個字符是否為 unicode 空格或分隔符. -
isAscii
判斷一個字符是否在 unicode 字母表的前 128 位。 -
isLatin1
判斷一個字符是否在 unicode 字母表的前 256 位. -
isAsciiUpper
判斷一個字符是否為大寫的 ascii 字符. -
isAsciiLower
判斷一個字符是否為小寫的 ascii 字符. -
ord
將字符轉(zhuǎn)換為數(shù)字chr
將數(shù)字轉(zhuǎn)換為字符
可以通過ord
和chr
來實(shí)現(xiàn)一個字符串平移的函數(shù)如:
import Data.Char
encode :: Int -> String -> String
encode salt msg = let msgs = map ord msg
| | digs = map (+ salt) msgs
| | in map chr digs
decode :: Int -> String -> String
decode salt msg = encode (-salt) msg
則輸入encode 1 "abc"
輸出"bcd"脉顿,同樣decode 1 "bcd"
輸出"abc"
Data.Map
Map是key不重復(fù)一種KV鍵值對集合(List)制市。
-
fromList
取一個關(guān)聯(lián)列表,返回一個與之等價的Map 弊予,eg:fromList [("a",1),("b",2)]
-
toList
是fromList
的反函數(shù) -
insert
插入一個新的KV,如:Map.insrt 3 100 Map.empty
-
size
返回Map的大小 -
singleton
返回只有一個KV的Map:Map.singleton 3 100
-
lookup
查詢對應(yīng)的鍵值 -
member
某個鍵是否存在一個Map中祥楣,返回Bool:Map.member 3 $ Map.fromList [(2,5),(4,5)]
-
map
Map中的map操作的是V值:Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)]
返回fromList [(1,100),(2,400),(3,900)] -
filter
同樣操作的是V值。 -
keys
返回一個由K組成的List -
elems
返回一個由V組成的List
Data.Set
Set中的數(shù)據(jù)是唯一的汉柒,元素是必須可排序的误褪。注意其因?yàn)楹虳ata.List很多的函數(shù)重復(fù),所以導(dǎo)入的時候使用import qulified Data.Set as Set
的方式碾褂。創(chuàng)建一個Set是通過fromList
函數(shù)兽间,其他函數(shù)就不列舉了。
創(chuàng)建自己的模塊
在根目錄創(chuàng)建geometry.hs正塌。
module Geometry
( sphereVolume,
sphereArea
) where
sphereVolume :: Float -> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)
sphereArea :: Float -> Float
sphereArea radius = 4 * pi * (radius ^ 2)
分級模塊創(chuàng)建方式,創(chuàng)建geometry/sphere.hs
則在sphere.hs中的內(nèi)容為:
module Geometry.Sphere
( volume嘀略,
area
) where
volume :: Float -> Float
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)
area :: Float -> Float
area radius = 4 * pi * (radius ^ 2)
構(gòu)造自己的Types和Typeclasses
Bool在標(biāo)準(zhǔn)函數(shù)庫的定義為:
data Bool = False | True
構(gòu)造自己的類型,一種方法就是使用data關(guān)鍵字乓诽。
如構(gòu)造一個圖形帜羊,該圖形可以是Circle也可以是Rectangle,定義為:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
上述Circle表示圓形鸠天,后邊的跟著的三個Float表示一個Circle由三個Float組成讼育。
創(chuàng)建完Shape后會自動生成Circle及Rectangle類型,此時查看其類型聲明為:
ghci> :t Circle
Circle::Float -> Float -> Float -> Shape
所以Circle 50 50
就是返回一個Shape類型,到這就明白了奶段,并不是先有Shape
后有Circle
饥瓷,而是現(xiàn)有Circle后有了Shape
同樣類型:data Maybe a = Nothing | Just a
中Nothing
并不是事先定義的,并且Nothing
是一個值構(gòu)造子痹籍,也不是類型呢铆。要創(chuàng)建個某個類型的值,必須使用后邊的值構(gòu)造子蹲缠,即你不能使用Shape關(guān)鍵字來創(chuàng)建一個Shape類型刺洒。
構(gòu)造的方式為:
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
注意看傳入的Circle和Rectangele的方式不一樣。
那么調(diào)用方式為:
ghci> surface $ Circle 10 20 30
ghci> surface $ Rectange 0 0 100 100
但是下面的調(diào)用時錯誤的
ghci>Circle 10 20 30
因?yàn)?code>Circle 10 20 30并不是Show類型吼砂,只有Show類型的數(shù)據(jù)才能被顯示。
那么修改構(gòu)造的方式為:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
即在定義的后邊加上deriving (Show)
.然后再調(diào)用就可以顯示出來了鼎文。
還可以這么定義:
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
這樣在定義函數(shù)的時候就應(yīng)該是:
surface::Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
那么調(diào)用就變?yōu)?
surface (Rectangle (Point 0 0) (Point 100 200))
前邊知道了如何導(dǎo)出函數(shù)的模塊渔肩,那么這種數(shù)據(jù)定義的模塊應(yīng)該怎么導(dǎo)入呢,方式如下:
module Shapes
( Point(..)
, Shape(..)
, surface
, nudge
, baseCircle
, baseRect
) where
即數(shù)據(jù)定義使用Shape(..)
的方式拇惋。..
表示將Shape
中的Circle
及Rectangle
類型都導(dǎo)出周偎,這樣外部就可以使用到這兩個構(gòu)造子,如果只導(dǎo)出Circle
則需要寫為Shape(Circle)
Record Syntax
定義一個人的名字撑帖,并且生成各種函數(shù):
data Person = Person {firstName :: String,
lastName::String,
age :: Int,
phoneNumber::String
} deriving (Show)
這樣就生成了Person類型以及firstName``lastName
..等等的函數(shù).
創(chuàng)建的時候則為
> Person {firstName="q",lastName="xg",age=10,phoneNumber="123456"}
并且打印Person的時候蓉坎,會將firstName等等顯示出來。
Type parameters
類型參數(shù),類似泛型胡嘿,如:
data Maybe a = Nothing | Just a
如果傳給Maybe的是Char,他就是Maybe Char類型蛉艾。如:Maybe 'a'
就是Maybe Char類型的。
而Nothing
也是Maybe a
類型衷敌,所以可以是Maybe Int
,也可以是Maybe String
再看一個例子:
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
...
Derived instances
上邊有涉及到Eq
,Ord
,Num
的typeclass等等類似Java interface的東東勿侯,比如Int
屬于Num
,但是如何實(shí)現(xiàn)這些interface呢缴罗,用java表達(dá)就是如何implement助琐,方法就是通過派生deriving
,使用data
創(chuàng)建類型的時候面氓,后續(xù)跟上deriving Num
就表明該類型屬于Num兵钮,并自動加上對應(yīng)的行為。
但是注意data創(chuàng)建的是instance舌界,而不是typeclass掘譬,且等號左邊是類型構(gòu)造子,等號右邊是值構(gòu)造子呻拌,一般在創(chuàng)建某種類型的值時屁药,使用的是值構(gòu)造子,而類型構(gòu)造子則用于函數(shù)聲明等位置處,類型構(gòu)造子無法來創(chuàng)建某一個值酿箭,一定要注意复亏,比如下邊創(chuàng)建了一個Person的類型,那么要創(chuàng)建一個Person類型的值缭嫡,就不能使用Person
創(chuàng)建缔御,而是要使用等號右邊的關(guān)鍵字(值構(gòu)造子)創(chuàng)建.
只要派生為Eq類,那么定義的數(shù)據(jù)類型就有可比性:
data Person = Person{firstName :: String,lastName :: String,age::Int} deriving Eq
haskell會檢查值構(gòu)造子是否一致妇蛀,再用==
檢查其中的所有數(shù)據(jù)(必須都是Eq的成員)是否一致耕突。
同樣也可以指定為多個類型:
data Person = Person{firstName :: String,lastName :: String,age::Int} deriving (Show,Eq,Read)
若將一個類型指定派生為某個A類型,則該類型的所有參數(shù)必須都屬于A類型评架,才可以進(jìn)行派生眷茁。
舉一個經(jīng)常會遇到的例子:
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
每個值構(gòu)造子都沒有參數(shù),因?yàn)榕缮薊q,所以具有可比性纵诞,派生了Ord上祈,所以可以比較大小,有Bounded浙芙,可以使用minBound
獲取下界登刺,有了Enum,可以使用succ
等函數(shù)進(jìn)行枚舉嗡呼。
在data聲明中纸俭,=
左邊是類型構(gòu)造子,=
右邊用|
分割的是值構(gòu)造子,要注意區(qū)分南窗。因?yàn)楹瘮?shù)生命中只能填寫類型揍很,如果分不清楚,可能就將值構(gòu)造子填入万伤,導(dǎo)致死都不知道怎么死的女轿。
注:不要在data中添加類型約束,即便看起來沒問題壕翩。
Type synonyms
給某個類型提供別名:
type String = [Char]
又如:
type PhoneNumber = Stirng
type Name = String
type PhoneBook = [(String,String)]
那么定義某個函數(shù)則為:
inPhoneBook::Name -> PhoneNumber -> PhoneBook -> Bool
而如果不定義別名蛉迹,則該函數(shù)為:
inPhoneBook::String -> String -> [(String,String)] -> Bool
定義別名也可以有參數(shù):
type AssocList k v = [(k,v)]
類型別名也可以定義不全的類型構(gòu)造子.
類型別名一般可以用于函數(shù)類型聲明或類型注釋上,如果要創(chuàng)建一個新類型放妈,不能用類型名+參數(shù)的方式去創(chuàng)建北救,一定要明白類型構(gòu)造子和值構(gòu)造子的區(qū)別。
類似函數(shù)在定義的時候進(jìn)行聲明芜抒,變量在定義的時候也可以進(jìn)行聲明珍策,所以將來::
看成通用的一類看待會更好理解這門語言。
如
a::Int
a=1
是可以工作的宅倒,同樣函數(shù)在定義的時候也是這種格式:
test::a->a
test a = a+1
只不過這個地方用到了泛型攘宙,你也可以這樣聲明
test :: Int -> Int
test a=a+1
遞歸地定義數(shù)據(jù)結(jié)構(gòu)
如List [1]
是1:[]
的語法糖,[1,2]
是1:2:[]
,[1,2,3]
是1:2:3:[]
可以看到List的類型類似這樣x:listx
其中x是匹配到的第一個元素蹭劈,而listx是一個匹配的新的list,每個List類型都可以匹配為x:listx
類型疗绣,并且每個listx都可以匹配為x:listx
類型,除了基礎(chǔ)類型[]
,所以在定義List的時候就可以通過遞歸來進(jìn)行定義铺韧,如將[]替換為empty,:
替換為Cons,那么定義List就可以是這樣:
data List a = Empty | Cons a List a deriving (Show ,Read,Eq,Ord)
那么在創(chuàng)建List的時候就可以這樣,1 `Cons` Empty
注意最后的Empty就已經(jīng)表明其是一個List,所以創(chuàng)建的遞歸數(shù)據(jù)類型一般都是需要最基礎(chǔ)的類型多矮,比如Empty,比如原始List的[]
等哈打。
Cons
也可以替換為一個符號塔逃,如
data List a = Empty | a :-: (List a) deriving (Show, Read,Eq, Ord)
那么如果要定義該符號的優(yōu)先級,需要加上infixr
關(guān)鍵字料仗,其后的數(shù)字表示優(yōu)先級:
infixr 5 :-:
data List a = Empty | a :-: List a deriving (Show, Read,Eq, Ord)
那么如果要做模式匹配的話湾盗,是可以這樣做的:
a:-:b = xxxx
原因就是:-:
是構(gòu)造子,而模式匹配就是通過構(gòu)造子來進(jìn)行匹配的立轧,同理[]
也是構(gòu)造子,:
也是構(gòu)造子格粪,所以你可以通過x:xs
來進(jìn)行匹配,注意模式匹配中匹配的都是值構(gòu)造子肺孵,一定不要寫類型。
二叉樹
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show,Read,Eq)
創(chuàng)建節(jié)點(diǎn)
singleton:: a -> Tree a
singleton x = Node x EmptyTree EmptyTree
插入節(jié)點(diǎn)
treeInsert::(Ord a)=> a->Tree a->Tree a
treeInesrt x EmptyTree = singleton x
treeInsert x (Node a left right)
| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x> a = Node a left (treeInsert x right)
查找節(jié)點(diǎn)
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
| x == a = True
| x < a = treeElem left
| x > a = treeElem right