5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixir 1.18の型推論を試してみる をやってみた

Last updated at Posted at 2025-02-02

こんにちは!
プログラミング未経験文系出身、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をインストールします。

bash
asdf install elixir 1.18.2

カレントディレクトリ配下のプロジェクトに対してのみv1.18.2を適用します。

bash
asdf local elixir 1.18.2

プロジェクトの作成

新しいプロジェクトを作成します

bash
mix new type_check
cd type_check

User モジュールの定義

lib/user.ex を作成します。
CHANGELOGに書いてあったものと同じ定義を書きます。

lib/user.ex
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です。

使用例

iex
user = %User{age: 20, car_choice: "Nissan"}
user.age
20

drive/2 関数を定義

下記でdrive/2関数を定義します。

drive/2when age >= 18の値次第で関数パターンマッチにより分岐されます。

age >= 18 のとき

def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do ... end ブロックを通ります。

また、car_choicesというリストも受け取るだろうと予想されます。
:age:car_choiceの値としてそれぞれagecarというパラメータ(実引数)を受け取ります。

何故なら変数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 を以下のように変更します

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{}}となっているためコンパイル時に型推論による警告が出るはずです。
確認してみましょう。

コンパイルの実行

警告をエラーとしてコンパイルを実行します

bash
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 のバージョン下限を編集します。

mix.exs
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のバージョンを変更します

bash
asdf local elixir 1.17.3

この状態でコンパイルします

bash
mix compile --force --warnings-as-errors

実行結果

Compiling 2 files (.ex)
Generated type_check app

1.17.3には型推論の機能が無いので、警告が出ずに問題なくコンパイルできました。

戻り値の型チェック

Elixir バージョンを 1.18.2 に戻します

bash
asdf local elixir 1.18.2

lib/type_check.ex を以下のように変更します

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では戻り値にも型推論が適用されます。
コンパイルしてみましょう。

bash
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のバージョンを再度変更します

bash
asdf local elixir 1.17.3

この状態でコンパイルします

bash
mix compile --force --warnings-as-errors

実行結果

Compiling 2 files (.ex)
Generated type_check app

1.17.3には型推論の機能が無いので、戻り値の型チェックでも警告が出ずに問題なくコンパイルできました。

~Elixirの国のご案内~

↓Elixirって何ぞや?と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ!私もエンジニア未経験から学習中です。

We Are The Alchemists, my friends!:bouquet:1
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。2

  1. @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。

  2. @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中!

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?