遙控車

遙控車

學習練習

簡介

Struct

Struct 是建立在 maps 之上的擴充機制,提供編譯期檢查與預設值。Struct 會以定義它的模組來命名。要定義 struct,請使用defstruct建構。這個建構通常緊接在模組定義之後。defstruct可以接受 atom 的陣列(用於nil預設值),或關鍵字陣列(用於指定的預設值)。沒有預設值的欄位必須排在具有預設值的欄位前面。

defmodule Plane do
  defstruct [:engine, wings: 2]
end

plane = %Plane{}
# => %Plane{engine: nil, wings: 2}

存取與更新欄位

由於 struct 建立在 map 之上,我們可以使用大多數的 map 函式來取得與操作值。struct 並未實作 Access Behaviour。建議改用_靜態存取運算子_.來存取 struct 的欄位。

  • 取得/擷取欄位值:

    plane = %Plane{}
    plane.engine
    # => nil
    Map.fetch(plane, :wings)
    # => {:ok, 2}
    
  • 更新欄位值

    plane = %Plane{}
    %{plane | wings: 4}
    # => %Plane{engine: nil, wings: 4}
    

模式配對

struct 可以用於模式配對,帶或不帶 struct 名稱都可以。

plane = %Plane{}
%Plane{wings: wings} = plane
%{wings: wings} = plane

在模式中帶上 struct 名稱,就能確保左右兩側都是相同型別的 struct。

defmodule Helicopter do
  defstruct [:engine, rotors: 1]
end

%Plane{} = %Helicopter{}
# => (MatchError) no match of right hand side value: %Helicopter{engine: nil, rotors: 1}

強制初始化欄位值

我們可以使用@enforce_keys模組屬性,搭配欄位鍵的陣列,確保 struct 建立時這些值就已經初始化。如果某個鍵沒有列在其中,它的值就會是nil,就像上面的範例一樣。如果強制要求的鍵沒有被初始化,就會引發錯誤。

defmodule User do
  @enforce_keys [:username]
  defstruct [:username]
end

%User{}
# => (ArgumentError) the following keys must also be given when building struct User: [:username]

說明

在這道練習裡,你可以玩玩一台遙控車,你終於存夠錢把它買下來了。

遙控車一開始的電量都是滿的(100%)。每次你用遙控器駕駛這台車,它就會前進 20 公尺,並消耗 1% 的電量。這台車的暱稱要等到建立之後才會知道。

這台遙控車有個炫麗的 LED 顯示器,會顯示兩項資訊:

  • 它總共行駛的距離,顯示為:"<METERS> meters"。
  • 剩餘的電量,顯示為:"Battery at <PERCENTAGE>%"。

如果電量剩下 0%,你就不能再駕駛這台車,電池顯示器會顯示 "Battery empty"。

1. 建立一台全新的遙控車

實作RemoteControlCar.new/0函式,回傳一個全新的遙控車 struct:

RemoteControlCar.new()
# => %RemoteControlCar{
#      battery_percentage: 100,
#      distance_driven_in_meters: 0,
#      nickname: "none"
#    }

struct 必須要有暱稱,請確保在new函式中初始化這個值,而不是在 struct 裡。

2. 建立一台帶有暱稱的全新遙控車

實作RemoteControlCar.new/1函式,回傳一個帶有指定暱稱的全新遙控車 struct:

RemoteControlCar.new("Blue")
# => %RemoteControlCar{
#      battery_percentage: 100,
#      distance_driven_in_meters: 0,
#      nickname: "Blue"
#    }

3. 顯示距離

實作RemoteControlCar.display_distance/1函式,回傳 LED 顯示器上顯示的距離:

car = RemoteControlCar.new()
RemoteControlCar.display_distance(car)
# => "0 meters"

請確認這個函式只接受RemoteControlCar struct 作為引數。

4. 顯示電量百分比

實作RemoteControlCar.display_battery/1函式,回傳 LED 顯示器上顯示的電量百分比:

car = RemoteControlCar.new()
RemoteControlCar.display_battery(car)
# => "Battery at 100%"

請確認這個函式只接受RemoteControlCar struct 作為引數。如果電量剩下 0%,電池顯示器會顯示 "Battery empty"。

5. 駕駛會改變電量與行駛距離

實作RemoteControlCar.drive/1函式,它會:

  • 將行駛距離增加 20 公尺
  • 消耗 1% 的電量
RemoteControlCar.new("Red")
|> RemoteControlCar.drive()

# => %RemoteControlCar{
#      battery_percentage: 99,
#      distance_driven_in_meters: 20,
#      nickname: "Red"
#    }

請確認這個函式只接受RemoteControlCar struct 作為引數。

6. 處理電池沒電時的駕駛行為

更新RemoteControlCar.drive/1函式,讓電池耗盡(0%)時,既不增加行駛距離,也不減少電量百分比:

%RemoteControlCar{
  battery_percentage: 0,
  distance_driven_in_meters: 1980,
  nickname: "Red"
}
|> RemoteControlCar.drive()

# => %RemoteControlCar{
#      battery_percentage: 0,
#      distance_driven_in_meters: 1980,
#      nickname: "Red"
#    }
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Elixir Exercism

準備好開始 遙控車 了嗎?

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