トラック
/
Elixir
Elixir
/
演習
/
ラジコンカー
ラジコンカー

ラジコンカー

学習演習

はじめに

構造体

構造体は、マップを基盤にした拡張で、コンパイル時のチェックとデフォルト値を提供します。構造体の名前は、それが定義されているモジュール名になります。構造体を定義するには、defstructを使います。この構文は通常、モジュール定義のすぐ後に書きます。defstructには、アトムのリスト(デフォルト値がnilの場合)またはキーワードリスト(デフォルト値を指定する場合)を渡します。デフォルト値のないフィールドは、デフォルト値のあるフィールドより前に置く必要があります。

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

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

フィールドへのアクセスと更新

構造体はマップを基盤にしているので、ほとんどのマップ関数を使って値の取得や操作ができます。構造体には、_Access Behaviour_は実装されていません。構造体のフィールドにアクセスするには、代わりに_静的アクセス演算子_の.を使うことをおすすめします。

  • フィールド値の取得

    plane = %Plane{}
    plane.engine
    # => nil
    Map.fetch(plane, :wings)
    # => {:ok, 2}
    
  • フィールド値の更新

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

パターンマッチング

構造体は、構造体名を付けてでも、付けなくても、パターンマッチングに使えます。

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

パターンに構造体名を含めることで、左辺と右辺がどちらも同じ型の構造体であることを保証できます。

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モジュール属性にフィールドキーのリストを指定すると、構造体の作成時に値が初期化されていることを保証できます。キーがリストに含まれていない場合、その値は上の例で見たように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パーセント消費します。車のニックネームは、車が作成されるまでわかりません。

このラジコンカーには、2つの情報を表示するおしゃれなLEDディスプレイが付いています。

  • これまでに走った合計距離。"<METERS> meters"のように表示されます。
  • バッテリーの残量。"Battery at <PERCENTAGE>%"のように表示されます。

バッテリーが0%になると、車はもう走らせられなくなり、バッテリー表示は"Battery empty"になります。

1. 新品のラジコンカーを作成する

新品のラジコンカーの構造体を返すRemoteControlCar.new/0関数を実装しましょう。

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

ニックネームは構造体で必須の項目です。値はnew関数の中で初期化し、構造体では初期化しないようにしてください。

2. ニックネームを指定して新品のラジコンカーを作成する

指定したニックネームを持つ新品のラジコンカーの構造体を返すRemoteControlCar.new/1関数を実装しましょう。

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

3. 走行距離を表示する

LEDディスプレイに表示されるのと同じ走行距離を返すRemoteControlCar.display_distance/1関数を実装しましょう。

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

この関数は、引数としてRemoteControlCar構造体のみを受け取るようにしてください。

4. バッテリー残量を表示する

LEDディスプレイに表示されるのと同じバッテリー残量を返すRemoteControlCar.display_battery/1関数を実装しましょう。

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

この関数は、引数としてRemoteControlCar構造体のみを受け取るようにしてください。バッテリーが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構造体のみを受け取るようにしてください。

6. バッテリー切れの状態での走行を考慮する

バッテリーが切れている(0%)ときは、走行距離を増やさず、バッテリー残量も減らさないようにRemoteControlCar.drive/1関数を更新しましょう。

%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を学んでマスターできます。すべて無料です。