揭露欄位名稱的 record
February 7, 2022在〈結合 sum 與 product 型態〉看到定義了 Point
與 Shape
型態:
data Point = Point Float Float deriving Show
data Shape = Triangle Point Float | Rectangle Point Float Float | Circle Point Float deriving Show
Point
實例會包含 x 與 y 座標資料,而 Shape
會包含幾何中心以及各自的邊長、長寬或半徑資訊,不過,到底哪個是哪個呢?沒有欄位名稱,實在是很難閱讀!
使用 record
Haskell 提供了 record 語法,可以讓你指定資料的各欄位名稱,例如:
data Point = Point {x :: Float, y :: Float} deriving Show
data Shape = Triangle {center :: Point, leng :: Float} |
Rectangle {center :: Point, leng :: Float, width :: Float} |
Circle { center :: Point, radius :: Float} deriving Show
你還是可以使用值建構式來建立實例:
ghci> let p = Point 0 0
ghci> let rect = Rectangle p 10 20
ghci> p
Point {x = 0.0, y = 0.0}
ghci> rect
Rectangle {center = Point {x = 0.0, y = 0.0}, leng = 10.0, width = 20.0}
ghci>
然而,也可以指定欄位名稱來建立實例:
ghci> let p = Point {x = 10, y = 20}
ghci> let rect = Rectangle {center = p, leng = 10, width = 20}
ghci> p
Point {x = 10.0, y = 20.0}
ghci> rect
Rectangle {center = Point {x = 10.0, y = 20.0}, leng = 10.0, width = 20.0}
ghci>
欄位在指定時,不一定要照著定義時的順序:
ghci> let p = Point {y = 20, x = 10}
ghci> p
Point {x = 10.0, y = 20.0}
ghci>
取值與更新
使用 record 定義資料型態後,Haskell 會以欄位名稱產生取值函式,例如,Point
會有 x
、y
函式:
ghci> :t x
x :: Point -> Float
ghci> :t y
y :: Point -> Float
ghci> let p = Point {x = 0, y = 5}
ghci> x p
0.0
ghci> y p
5.0
ghci>
若多個值建構式有相同的欄位名稱,例如 Triangle
、Rectangle
、Circle
都有 center
欄位,那麼取值函式接受的參數型態會是 Shape
:
ghci> :t center
center :: Shape -> Point
ghci> let p = Point {x = 0, y = 5}
ghci> let circle = Circle {center = p, radius = 10}
ghci> center circle
Point {x = 0.0, y = 5.0}
ghci>
可以指定某個欄位「更新」資料,當然,因為 Haskell 的不可變動特性,實際上是建立了一個新值,其中有指定的新欄位值:
ghci> let p1 = Point {x = 0, y = 5}
ghci> let p2 = p1{x = 5}
ghci> p1
Point {x = 0.0, y = 5.0}
ghci> p2
Point {x = 5.0, y = 5.0}
ghci>
模式比對
你還是可以透過模式比對來取得各欄位的值,雖然使用了 record 語法,建構實例時若以欄位名稱來指定欄位值時,可以不用照定義欄位名稱時的順序,不過,順序依然是結構的一部份,基本的模式比對時,還是必須結構來拆解:
ghci> let Point px py = p
ghci> px
0.0
ghci> py
5.0
ghci> let Circle _ radius = circle
ghci> radius
10.0
ghci>
然而,使用 record 語法定義的資料型態,模式比對時,可以指定欄位名稱:
ghci> let Point{x = px, y = py} = p
ghci> px
0.0
ghci> py
5.0
ghci> let Point{y = py} = p
ghci> py
5.0
ghci>
透過模式比對將欄位名稱將欄位值指定給變數時,變數是置於 =
的右邊,別搞錯了!實際上,你也可以 let Point{x, y} = p
,這會將 x
欄位的值指定給 x
變數,y
欄位的值指定給 y
變數,不過,這會直接遮蔽原本的取值函式,因此通常會用於函式上:
xPlus10 :: Point -> Float
xPlus10 Point{x} = x + 10