LoginSignup
3
1

なぜアトム型のキーを持つマップにおいて、値を参照するときにキーをstring型のように扱う場合があるのか?

Posted at

疑問

なぜ、アトム型のキーを持つマップにおいて、値を参照するときにキーをstring型のように扱う場合があるのか?

以下のようなタイプスペック時にq_task_timeのキーであるstart_datetimeはアトムであるはずですが、string型のような参照の仕方をします。

疑問に思ったので、ドキュメントと照らし合わせながら理由を紐解いていきました。

タイプスペックは以下

  @type q_task_time :: %{
          start_datetime: NaiveDateTime.t() | nil,
          end_datetime: NaiveDateTime.t() | nil
        }
  def update_q_task_times(%__MODULE__{id: id} = editor, ans_editor_id, :start)
      when id == ans_editor_id do
    q_task_times = List.insert_at(editor.q_task_times, 0, %{"start_datetime" => now(), "end_datetime" => nil})
    editor = %{editor | q_task_times: q_task_times}

    {:ok, %Ans{id: ans_id}} =
      Answers.save_user_mb_ques_answer(editor.ans, Map.from_struct(editor))

    %{editor | ans: Answers.get_user_mb_ques_answer(ans_id)}
  end
  

理由

Postgres(DB)ではアトムは許容しないためStringに変換されるから

マップ キーはアトムではなく文字列または整数にすることをお勧めします。マップのシリアル化方法によってはアトムが受け入れられる場合もありますが、セキュリティ上の理由から、データベースは常にアトム キーを文字列に変換します。

スクリーンショット 2024-05-21 16.11.24.png

参考:hexdoc ecto.schema module-primary-keys

疑問

タイプスペックのキーはなぜString型で書かれていないのか?

以下タイプスペックにおいて、マップをアトムのキーで書く書き方がOKですが、Stringのキーで書く書き方はKernel.TypespecErrorが出ます。

OKな書き方

 @type q_task_time :: %{start_datetime: NaiveDateTime.t() | nil, end_datetime: NaiveDateTime.t() | nil}
 @type q_task_time :: %{:start_datetime => NaiveDateTime.t() | nil, :end_datetime=> NaiveDateTime.t() | nil}

NGな書き方

 @type q_task_time :: %{"start_datetime" => NaiveDateTime.t() | nil, "end_datetime"=> NaiveDateTime.t() | nil} # Kernel.TypespecError

理由

文字列のキーの型定義ができないから

明示的には書かれていませんが、型スペックでマップのキーとして使えるのは主にアトムや整数であることが暗黙の了解となっています。
理由はチェックやパターンマッチングの際に効率的に処理できるためです。

[参考: hexdoc typespecs reference]
(https://hexdocs.pm/elixir/1.16.3/typespecs.html)

基本の復習

マップとは

  • キーにアトムを強制されない
  • 要素の順が保証されない
  • キーがアトムだった場合のみ呼び出し時に.(ドット演算子)が使える
  • =>ではなく:を使った書き方の場合、キーがアトムの場合:は省略できる
map = %{:apple => 200, "orange" => 400}

map.apple

map["orange"]

map2 = %{banana: 200}

map2[:banana]

map2.banana

タイプスペックとは

Elixirのタイプスペック(typespec)は、関数やデータ構造の型情報を明示するための仕様です。タイプスペックを使うことで、コードの可読性と信頼性を向上させ、ドキュメント生成や静的解析ツールの活用が容易になります。

Elixirは動的型付け言語ですが、タイプスペックを使うことで静的型付け言語のように扱えます。雑に例えるとJavaScriptからTypeScriptへの変換のようなものです。

例1. 型エイリアスの定義


@type user_id :: integer
@type user :: %{
  id: user_id,
  name: String.t(),
  email: String.t()
}

例2.関数のタイプスペック

@spec add(integer, integer) :: integer
def add(a, b) do
  a + b
end

@spec get_user(user_id) :: user | nil
def get_user(id) do
  # ユーザーを取得するロジック
end

例3.モジュールのタイプスペック

defmodule UserManager do
  @type user_id :: integer
  @type user :: %{
    id: user_id,
    name: String.t(),
    email: String.t()
  }

  @spec create_user(user_id, String.t(), String.t()) :: user
  def create_user(id, name, email) do
    %{
      id: id,
      name: name,
      email: email
    }
  end

  @spec get_user_name(user) :: String.t()
  def get_user_name(user) do
    user.name
  end
end


まとめ

ややこしですがEctotypespecとは別物です。
Typespecの目的は型の解析と静的型チェックEctoの目的はデータベースのデータを扱うためのライブラリです。

3
1
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
3
1