《Haskell趣學指南》筆記之自定義類型

系列文章編程


自定義數據類型

1、用 data 關鍵字

data 類型名 = 值構造器 | 值構造器
data Bool = False | True
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
複製代碼

值構造器能夠直接是一個值,如 True / False 值構造器也能夠是一個名字後面加一些類型數據結構

值構造器本質上是一個返回某數據類型值的函數,因此 Circle 和 Rectangle 不是類型,是函數:編程語言

ghci> :t Circle 
Circle :: Float -> Float -> Float -> Shape 
ghci> :t Rectangle 
Rectangle :: Float -> Float -> Float -> Float -> Shape
複製代碼

而後就能夠使用這個類型了函數

area :: Shape -> Float 
-- 注意下面的模式匹配
area (Circle _ _ r) = pi * r ^ 2 
area (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
-- 注意下面的 Circle 和 Reactangle 的位置
ghci> area $ Circle 10 20 10 
314.15927 
ghci> area $ Rectangle 0 0 100 100 
10000. 0
複製代碼

可是如今若是你在 ghci 裏輸入 Circle 1 1 5 會報錯,由於 Shape 不是 Show 類型類的實例,不能被 show 函數調用。 解決辦法是在 data Shape 那句話的後面加一句 deriving (Show)post

data Shape = Circle Float Float Float | Rectangle Float Float Float Float  
    deriving (Show)
複製代碼

改進優化

Circle 函數接受三個 Float 參數,這三個參數前面兩個是圓心的座標,最後一個是半徑。spa

咱們用 Point 類型來優化 Shape,使得它更已讀:3d

data Point = Point Float Float deriving (Show) 
-- 注意左邊的 Point 是類型名,右邊的 Point 是值構造器名(相似與構造函數麼?)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

area :: Shape -> Float 
area (Circle _ r) = pi * r ^ 2 
-- 注意下面的模式匹配
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

ghci> area (Rectangle (Point 0 0) (Point 100 100)) 
10000. 0 
ghci> area (Circle (Point 0 0) 24) 
1809. 5574
複製代碼

導出code

module Shapes ( 
    Point(..) , -- 看這裏
    Shape(..) , -- 看這裏
    area , 
) where
複製代碼

其中 Shape(..) 的意思是導出 Shape 以及 Shape 全部的值構造器,也能夠寫成 Shape(Circle, Rectangle)。 固然也能夠不寫括號這一部分,這樣別人就不能使用 Circle 和 Rectangle 函數了。遞歸

2、用 data + 記錄語法 record syntax

data Person = Person { firstName::String, age::Int, height::Float, phoneNumber::String, flavor::String } deriving (Show)
複製代碼

這種語法會自動建立 firstName 等函數、容許按字段取值。

ghci> :t firstName 
firstName :: Person -> String
複製代碼

類型參數(很像泛型)

data Maybe a = Nothing | Just a
複製代碼

Maybe 是一個類型構造器(不是類型),a 是類型參數,a 能夠是 Int / Char / ...,而 Just 是個函數。

因爲 Haskell 支持類型推導,因此咱們只用寫 Just 'a',Haskell 就知道這是一個 Maybe Char 類型。

其實列表 [] 就是一個類型構造器,[Int] 存在,可是不存在類型 []。

Maybe 類型的使用示例:

ghci> Just "Haha"
Just "Haha" 
ghci> :t Just "Haha" 
Just "Haha" :: Maybe [Char] 
ghci> :t Just 84 
Just 84 :: (Num t) => Maybe t 
ghci> :t Nothing 
Nothing :: Maybe a 
ghci> Just 10 :: Maybe Double 
Just 10. 0 
複製代碼

data 支持類約束,可是永遠不要用

data (Ord k) => Map k v = ...
複製代碼

書上說這隻會徒增無謂的代碼。

如何讓一個 type 成爲類型類的實例

只須要在 data 語句後面加上 deriving (Eq) 便可。

在一個類型派生爲Eq的實例後,就能夠直接使用==或/=來判斷它們的值的相等性了。 Haskell會先檢查兩個值的值構造器是否一致(這裏只有單值構造器),再用==來檢查其中的每一對字段的數據是否相等。 惟一的要求是:其中全部字段的類型都必須屬於Eq類型類。

加上 deriving (Eq, Show, Read) 就能夠成爲三者的實例。

Enum 類型類

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
-- 或者加上 typeclass
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday           
    deriving (Eq, Ord, Show, Read, Bounded, Enum) -- 綜合目前所學
    
ghci> Wednesday
Wednesday 
ghci> show Wednesday
"Wednesday" 
ghci> read "Saturday" :: Day 
Saturday
ghci> Saturday == Sunday 
False 
ghci> Saturday == Saturday 
True 
ghci> Saturday > Friday 
True 
ghci> Monday ` compare` Wednesday 
LT
ghci> minBound :: Day 
Monday 
ghci> maxBound :: Day 
Sunday
複製代碼

類型別名

type String = [Char] -- 注意不是 data 是 type
-- 支持參數
type IntMap v = Map Int v
-- 等價於 point-free 風格的下面代碼
type IntMap = Map Int
複製代碼

Either a b 類型

data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
複製代碼

書上例子挺好懂,大概意思是錯了就返回 Left "error message",對了就返回 Right "data message"。 不過我還不明白我怎麼知道 Right "data message" 是 Right 構造出來的呢?

遞歸數據結構

data List a = Empty | Cons a (List a) 
    deriving (Show, Read, Eq, Ord)
    
ghci> Empty 
Empty 
ghci> 5 ` Cons` Empty 
Cons 5 Empty
ghci> 4 ` Cons` (5 ` Cons` Empty) 
Cons 4 (Cons 5 Empty) 
ghci> 3 ` Cons` (4 ` Cons` (5 ` Cons` Empty)) 
Cons 3 (Cons 4 (Cons 5 Empty))
複製代碼

自制一個列表

infixr 5 :-: 
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)

ghci> 3 :-: 4 :-: 5 :-: Empty 
3 :-: (4 :-: (5 :-: Empty)) 
ghci> let a = 3 :-: 4 :-: 5 :-: Empty 
ghci> 100 :-: a 
100 :-: (3 :-: (4 :-: (5 :-: Empty)))

infixr 5  ^++ 
(^++) :: List a -> List a -> List a 
    Empty ^++ ys = ys 
    (x :-: xs) ^++ ys = x :-: (xs ^++ ys)

ghci> let a = 3 :-: 4 :-: 5 :-: Empty 
ghci> let b = 6 :-: 7 :-: Empty 
ghci> a ^++ b 
3 :-: (4 :-: (5 :-: (6 :-: (7 :-: Empty))))
複製代碼

從這個例子我大概理解黃玄說的『函數式就是 symbolism』

這一年裏一直在不斷刷新本身對「FP 是什麼」這個問題的回答… 以前以爲說「靠近/源自數學或者邏輯」吧,難道命令式/OO 的語言就不是描述數學和邏輯? 這種解釋自己不明白這個差異的人大概聽了也仍是不會明白…… 今天忽然以爲「(儘量的)symbolism(符號主義)」也是個不錯的描述,從 FP 語言的歷史來看,主要的兩個祖宗 Lisp 和 ML(LCF)都起家於符號主義 AI。 即便編程語言都是符號化的,但相比於寄託於各種外置的做用,越是「FP」越是 儘量得但願程序的行爲是能夠從符號中詳盡的,這所以帶來了你們說的「聲明式」、「可預測性」和「肯定性」。

自制一棵二叉搜索樹

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
-- 下面這個函數用來建立節點
singleton :: a -> Tree a 
singleton x = Node x EmptyTree EmptyTree 
-- 下面這個函數用來插入節點
treeInsert :: (Ord a) => a -> Tree a -> Tree a 
treeInsert x EmptyTree = singleton x 
treeInsert x (Node a left right)       
    | x == a = Node x left right      
    | x < a &emsp;= Node a (treeInsert x left) right &emsp; &emsp; &emsp;
    | x > a &emsp;= Node a left (treeInsert x right)
-- 下面這個函數用來判斷元素是否在樹中 
treeElem :: (Ord a) => a -> Tree a -> Bool 
treeElem x EmptyTree = False 
treeElem x (Node a left right) &emsp; &emsp; &emsp;
    | x == a = True &emsp; &emsp; &emsp;
    | x < a = treeElem x left &emsp; &emsp; &emsp;
    | x > a = treeElem x right
    
-- 使用

ghci> let nums = [8, 6, 4, 1, 7, 3, 5] 
ghci> let numsTree = foldr treeInsert EmptyTree nums 
ghci> numsTree 
Node 
    5
    (Node 3
        (Node 1 EmptyTree EmptyTree)
        (Node 4 EmptyTree EmptyTree)
    )
    (Node 7
        (Node 6 EmptyTree EmptyTree)
        (Node 8 EmptyTree EmptyTree)
    )
複製代碼
相關文章
相關標籤/搜索