揭露欄位名稱的 record

February 7, 2022

在〈結合 sum 與 product 型態〉看到定義了 PointShape 型態:

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 會有 xy 函式:

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>

若多個值建構式有相同的欄位名稱,例如 TriangleRectangleCircle 都有 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

分享到 LinkedIn 分享到 Facebook 分享到 Twitter