跟風者

跟風者

學習練習

簡介

記錄

記錄是一種資料結構,把相關的資訊連同標籤歸類在一起。 它們類似 JavaScript 或 Java 裡的物件,或是 C 或 Rust 裡的 struct,但有一些關鍵的差異。

建立記錄

建立記錄時要使用大括號,而記錄中的元素以逗號分隔。

firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

記錄的型別也用類似的語法定義,只是等號=換成了冒號:。

firefly : { name : String, creator : String, episodes : Int }
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

重複寫這些型別定義很麻煩,所以用型別別名來共用定義是常見的做法。

type alias TvSeries =
    { name : String
    , creator : String
    , episodes : Int
    }

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

型別別名也會自動為該記錄提供一個建構函式。 這有時會讓人意外,因為 Elm 的函式必須以小寫字母開頭,而型別建構函式正是其中一個例外。

firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14

存取記錄的欄位

存取記錄實例的欄位值的主要方式,是使用像firefly.creator這樣的點號記法。 Elm 編譯器也支援以點.開頭的特別存取函式,例如.creator,可用於任何具有同名欄位的記錄。 這是「函式必須以小寫字母開頭」這條規則的第二個例外。 事實上,使用者自訂的函式必須以小寫字母開頭,但我們剛剛看到的兩個例子是由編譯器產生的,而不是程式設計師寫的。

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

firefly.name
    --> "Firefly"

.creator firefly
    --> "Joss Whedon"

記錄也可以在綁定中被_解構_,這要用到記錄模式比對。 只要記錄具有這些名稱的欄位,就可以解構。

episodesCount : TvSeries -> Int
episodesCount { episodes } =
    episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
    let
        { name, creator } = show
    in
    { name = name
    , creator = creator
    , episodes = episodesCount show
    }

更新記錄

記錄和 Elm 裡的所有東西一樣都是不可變的,所以一旦定義了記錄,它的欄位值就永遠不會改變。 有一種特別的語法可以建立現有記錄的副本,同時改變其中一個或多個欄位。 這種語法使用直線符號|來區分原始記錄與要改變的欄位,例如{ oldRecord | field1 = newValue }。

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

updatedFirefly : TvSeries
updatedFirefly =
    { firefly | creator = "J Whedon", episodes = 15 }

比較記錄

Elm 使用[結構相等][equality],意思是同一個記錄的兩個實例若有相同的值,就相等。

firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
    --> True

可擴充記錄

Elm 也支援結構型別,意思是如果某個函式需要一個具有 x 和 y 欄位的記錄,它就能用於任何具有這些欄位的記錄,例如 2D 點、3D 點、太空船等等。 這類型別在定義中有一個直線符號|,例如{ a | x : Float, y : Float },在 Elm 的術語中稱為「可擴充記錄」。 要注意型別定義中的直線符號|與實際記錄實例中的直線符號|之間的差異:前者是可擴充記錄的定義({ a | x : Int }),後者表示我們正在更新該記錄的某些欄位。

point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
    sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
    { vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }

說明

在這個練習中,你是個超級運動迷,而且最近剛愛上 NBA 籃球。 由於才剛接觸 NBA 籃球,你正深入研究 NBA 的歷史,追蹤各支球隊、教練、他們的勝敗紀錄,並互相比較。

因為你還沒有支持的球隊,你也會開發一套演算法,判斷是否該為某支球隊加油。 你有七個任務,幫助你開發出專屬於你的「為球隊加油」演算法。

1. 定義模型

定義 Coach 型別別名,用來表示一筆記錄,包含以下兩個欄位,順序如下:

  • name:教練的名字,型別為String。
  • formerPlayer:表示該教練是否曾是球員,型別為Bool。

定義 Stats 型別別名,用來表示一筆記錄,包含以下兩個欄位,順序如下:

  • wins:勝場數,型別為Int。
  • losses:敗場數,型別為Int。

定義 Team 型別別名,用來表示一筆記錄,包含以下三個欄位,順序如下:

  • name:球隊的名字,型別為String。
  • coach:球隊的教練,型別為Coach。
  • stats:球隊的戰績,型別為Stats。

2. 建立球隊

你想與某些第三方程式碼介接,那些程式碼也有球隊的概念,但它們呼叫你的程式碼時,參數順序不同。請新增一個createTeam函式,型別簽章如下:String -> Stats -> Coach -> Team。

3. 更換教練

NBA 的老闆們沒什麼耐性,你發現球隊戰績不佳往往會導致教練被換掉。 實作replaceCoach函式,它接收新教練和球隊作為參數,並回傳換上新教練的球隊:

let
    coach = { name = "Larry Bird", formerPlayer = True }
    stats = { wins = 58, losses =  24 }
    team = { name = "Indiana Pacers", coach = coach, stats =  stats }
    newCoach = { name = "Isiah Thomas", formerPlayer = True }
in
replaceCoach newCoach team
--> { name = "Indiana Pacers"
--  , coach = { name = "Isiah Thomas", formerPlayer = True }
--  , stats = { wins = 58, losses = 24 } }

4. 檢查你是否該為某支球隊加油

未來你打算想出一套複雜的演算法來判斷是否該為某支球隊加油,但第一次嘗試時,你決定只要球隊的勝場數多於敗場數,你就該為它加油。

你希望這個函式能在其他情境中重複使用,所以在型別簽章中使用可擴充的記錄,以接受任何具有相關欄位的記錄。 為了讓函式盡可能簡潔,你想用模式比對從函式參數中取出stats。 實作rootForTeam函式,它接收一支球隊,如果你該為這支球隊加油就回傳True,否則回傳False。

let
    spursCoach = { name = "Gregg Popovich", formerPlayer =  False }
    spursStats = { wins = 56, losses = 26 }
    spursTeam = { name = "San Antonio Spurs", coach = spursCoach, stats = spursStats }
in
rootForTeam spursTeam
--> True
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Elm Exercism

準備好開始 跟風者 了嗎?

註冊 Exercism,透過 28 個概念110 個練習 和真人引導來學習並精通 Elm,全部免費。