こんにちは!
プログラミング未経験文系出身、Elixirの国に迷い込んだ?!見習いアルケミストのaliceと申します。
今回はElixir1.18の新機能である型推論について学んだことをまとめます。こちらを自分の環境でやってみた記事です。
元記事様(Thx for @RyoWakabayashi🎉)
目的
Elixir 1.18の新機能である型推論の機能を試したい。
実行環境
Windows 11 + WSL2 + Ubuntu 22.04
Elixir v1.18.2 および v1.17.3をasdfで切替
Erlang v27.0
型推論やってみた
Elixir 1.18.2 のインストール
検証時点で最新のElixir1.18.2をインストールします。
asdf install elixir 1.18.2
カレントディレクトリ配下のプロジェクトに対してのみv1.18.2を適用します。
asdf local elixir 1.18.2
プロジェクトの作成
新しいプロジェクトを作成します
mix new type_check
cd type_check
User モジュールの定義
lib/user.ex
を作成します。
CHANGELOGに書いてあったものと同じ定義を書きます。
defmodule User do
defstruct [:age, :car_choice]
def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do
if car in car_choices do
{:ok, car}
else
{:error, :no_choice}
end
end
def drive(%User{}, _car_choices) do
{:error, :not_allowed}
end
end
この部分のコードを噛み砕いてみます。
%User{}を定義
defstruct [:age, :car_choice]
でUserという構造体を定義します。
構造体%User{}
には:age
と:car_choice
の2つのフィールド(キー)があります。
:age
と:car_choice
のデフォルト値は未記載のためnilです。
使用例
user = %User{age: 20, car_choice: "Nissan"}
user.age
20
drive/2 関数を定義
下記でdrive/2
関数を定義します。
drive/2
はwhen age >= 18
の値次第で関数パターンマッチにより分岐されます。
age >= 18 のとき
def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do ... end
ブロックを通ります。
また、car_choices
というリストも受け取るだろうと予想されます。
:age
と:car_choice
の値としてそれぞれage
とcar
というパラメータ(実引数)を受け取ります。
何故なら変数car_choice
が使われているif文においてin/2
(in演算子)を使っているからです。
in/2
は第一引数が第二引数に含まれるかを示す関数です。
このことからin/2
の第二引数car_choice
はリストか範囲(range)かのいずれかであると推測されるものの、rangeは数字しかとらないため除外。
消去法でcar_choices
はリストであると推測されます。
age < 18 のとき
def drive(%User{}, _car_choices) do ... end
ブロックを通ります。
{:error, :not_allowed}
を返します。
メインモジュールの変更
lib/type_check.ex
を以下のように変更します
defmodule TypeCheck do
def hello do
car_choices = [:toyota]
User.drive({:ok, %User{}}, car_choices)
end
end
drive/2の定義から、User.driveの第一引数は%User{}であるべきなのですが、ここでは第一引数が{:ok, %User{}}
となっているためコンパイル時に型推論による警告が出るはずです。
確認してみましょう。
コンパイルの実行
警告をエラーとしてコンパイルを実行します
mix compile --force --warnings-as-errors
実行結果
Compiling 2 files (.ex)
warning: incompatible types given to User.drive/2:
User.drive({:ok, %User{age: nil, car_choice: nil}}, car_choices)
given types:
{:ok, %User{age: nil, car_choice: nil}}, non_empty_list(:toyota)
but expected one of:
dynamic(%User{age: term(), car_choice: term()}), dynamic()
where "car_choices" was given the type:
# type: non_empty_list(:toyota)
# from: lib/type_check.ex:24:17
car_choices = [:toyota]
typing violation found at:
│
25 │ User.drive({:ok, %User{}}, car_choices)
│ ~
│
└─ lib/type_check.ex:25:10: TypeCheck.hello/0
Compilation failed due to warnings while using the --warnings-as-errors option
警告が出て、--warnings-as-errors
オプションのためコンパイルに失敗しました。
1.17 でのコンパイル確認(引数の型チェック)
mix.exs
Elixir のバージョン下限を編集します。
defmodule TypeCheck.MixProject do
use Mix.Project
def project do
[
app: :type_check,
version: "0.1.0",
- elixir: "~> 1.18.2",
+ elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
...
使用するElixirのバージョンを変更します
asdf local elixir 1.17.3
この状態でコンパイルします
mix compile --force --warnings-as-errors
実行結果
Compiling 2 files (.ex)
Generated type_check app
1.17.3には型推論の機能が無いので、警告が出ずに問題なくコンパイルできました。
戻り値の型チェック
Elixir バージョンを 1.18.2 に戻します
asdf local elixir 1.18.2
lib/type_check.ex
を以下のように変更します
defmodule TypeCheck do
+ require Logger
def hello do
+ user = %User{age: 18, car_choice: :toyota}
car_choices = [:toyota]
- User.drive({:ok, %User{}}, car_choices)
+ case User.drive(user, car_choices) do
+ {:ok, car} -> car
+ :error -> Logger.error("User cannot drive")
end
end
end
これはぱっと見、「case文を使用してdrive/2
の戻り値次第で変数car
または文字列"User cannot drive"
を返す」実装に見えますが、実際にはdrive/2
の戻り値は下記のいずれかしかとりません。
- {:ok, car}
- {:error, :no_choice}
- {:error, :not_allowed}
つまり、:error -> Logger.error("User cannot drive")
部分を通ることがありません。
v1.18では戻り値にも型推論が適用されます。
コンパイルしてみましょう。
mix compile --force --warnings-as-errors
実行結果
Compiling 2 files (.ex)
warning: the following clause will never match:
:error
because it attempts to match on the result of:
User.drive(user, car_choices)
which has type:
dynamic({:error, :no_choice or :not_allowed} or {:ok, term()})
typing violation found at:
│
29 │ :error -> Logger.error("User cannot drive")
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
│
└─ lib/type_check.ex:29: TypeCheck.hello/0
Compilation failed due to warnings while using the --warnings-as-errors option
戻り値の型が不正であるため、警告が出て、--warnings-as-errors
オプションのためコンパイルに失敗しました。
1.17 でのコンパイル確認(戻り値の型チェック)
使用するElixirのバージョンを再度変更します
asdf local elixir 1.17.3
この状態でコンパイルします
mix compile --force --warnings-as-errors
実行結果
Compiling 2 files (.ex)
Generated type_check app
1.17.3には型推論の機能が無いので、戻り値の型チェックでも警告が出ずに問題なくコンパイルできました。
~Elixirの国のご案内~
↓Elixirって何ぞや?と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます
↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ!私もエンジニア未経験から学習中です。
↓We Are The Alchemists, my friends!1
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。2
-
@torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。 ↩