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


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

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

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

タイプスペックは以下
```elixir
  @type q_task_time :: %{
          start_datetime: NaiveDateTime.t() | nil,
          end_datetime: NaiveDateTime.t() | nil
        }
```

```elixir
  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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/c094e006-74a6-bb2b-c606-1164674901e7.png)


[参考:hexdoc ecto.schema module-primary-keys](https://hexdocs.pm/ecto/Ecto.Schema.html#module-primary-keys)

# 疑問
## タイプスペックのキーはなぜ`String`型で書かれていないのか？

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

## OKな書き方

```elixir
 @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な書き方
```elixir
 @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)

# 基本の復習
## マップとは
- キーにアトムを強制されない
- 要素の順が保証されない
- キーがアトムだった場合のみ呼び出し時に`.`(ドット演算子)が使える
- `=>`ではなく`:`を使った書き方の場合、キーがアトムの場合`:`は省略できる

```elixir
map = %{:apple => 200, "orange" => 400}

map.apple

map["orange"]

map2 = %{banana: 200}

map2[:banana]

map2.banana
```

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

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

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

```elixir

@type user_id :: integer
@type user :: %{
  id: user_id,
  name: String.t(),
  email: String.t()
}
```
### 例2.関数のタイプスペック

```elixir
@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.モジュールのタイプスペック
```elixir
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


```

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