LoginSignup
6
2
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【TIPS】構造体(struct)とマップの相互変換

Last updated at Posted at 2024-01-01

この記事は、Elixir Advent Calendar 2023 シリーズ13 の12日目です


【本コラムは、6分で読め、6分で試せます】

piacere です、ご覧いただいてありがとございます :bow:

構造体(struct)は、ユーザー定義のデータ型を作れて便利ですが、内部実装がマップなのに、マップ操作が一部できなかったりと、不便な面もあるので、その攻略法の1つとして、構造体とマップの相互変換についてまとめます

構造体 → マップ

逆説的ですが、構造体をマップに変換してしまえば、構造体の制約は気にしなくて良くなります(ただし、構造体のみ持っているキー制約が効かなくなります)

iex> %Range{first: 1, last: 10, step: 2} |> Map.from_struct
%{first: 1, last: 10, step: 2}

iex> NaiveDateTime.utc_now() |> Map.from_struct
%{
  calendar: Calendar.ISO,
  day: 1,
  hour: 8,
  microsecond: {263337, 6},
  minute: 25,
  month: 1,
  second: 52,
  year: 2024
}

上記例は、レンジや ~N[~] 等の日時系が実は構造体で出来ているハックでもあるので、下記も通ります

iex> 1..10//2 |> Map.from_struct
%{first: 1, last: 10, step: 2}

iex> ~N[2024-01-01 08:26:31.532047] |> Map.from_struct
%{
  calendar: Calendar.ISO,
  day: 1,
  hour: 8,
  microsecond: {532047, 6},
  minute: 26,
  month: 1,
  second: 31,
  year: 2024
}

構造体か否か判定

レンジや日時系以外にも、EctoやPhoenix、Nx等で使われているデータ型の多くは、表示上が構造体に見えなくても中身は構造体ですが、下記で判定できます

iex> 1..10//2 |> is_struct
true

iex> ~N[2024-01-01 08:26:31.532047] |> is_struct
true

iex> %{first: 1, last: 10, step: 2} |> is_struct
false

構造体のデバッグ

iex>  1..10//2 |> inspect(structs: false)
"%{__struct__: Range, first: 1, last: 10, step: 2}"

iex> ~N[2024-01-01 08:26:31.532047] |> inspect(structs: false)
"%{__struct__: NaiveDateTime, calendar: Calendar.ISO, day: 1, hour: 8, microsecond: {532047, 6}, minute: 26, month: 1, second: 31, year: 2024}"

iex> %{first: 1, last: 10, step: 2} |> inspect(structs: false)
"%{first: 1, last: 10, step: 2}"

このテクニックは、書籍「プログラミングElixir」 のP332(第一版だとP268)で解説されています

image.png

image.png

マップ → 構造体

野良のマップを構造体にコンバートしたり、構造体が持っているキー制約を効かせたいときは、下記で変換できます

iex> struct(Range, %{first: 1, last: 10, step: 2})
1..10//2

iex> struct(Range, %{})
nil..nil//nil

iex> %{first: 1, last: 10, step: 2} |> then(& struct(Range, &1))
1..10//2

iex> struct(NaiveDateTime, %{year: 2024, month: 1, day: 1, hour: 8, minute: 26, second: 31})
~N[2024-01-01 08:26:31]

構造体に存在しないキーは切り捨てられます

iex> struct(Range, %{first: 1, last: 10, step: 2, no_exist: "No Range, No Life"})
1..10//2

iex> struct(Range, %{first: 1, last: 10, step: 2, no_exist: "No Range, No Life"}) |> inspect(structs: false)
"%{__struct__: Range, first: 1, last: 10, step: 2}"

NaiveDateTimesecond まで削るとエラーになりますが、これは構造体自体の仕様では無く、各構築時のチェックになります

iex> struct(NaiveDateTime, %{year: 2024, month: 1, day: 1, hour: 8, minute: 26})
#Inspect.Error<
  got FunctionClauseError with message:

      """
      no function clause matching in Calendar.ISO.time_to_string/5
      """

その証拠に、レンジ等では型に適合しなそうな場合でも、エラーにはならず、ムリやり構築されます

iex> struct(Range, %{first: "This is first", last: 10, step: 2})
"This is first"..10//2

構築はされるものの、マトモに使える状態では無いです

iex> struct(Range, %{first: 1, last: 10, step: 2}) |> Enum.to_list              
[1, 3, 5, 7, 9]
iex> struct(Range, %{first: "This is first", last: 10, step: 2}) |> Enum.to_list
[]
6
2
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
6
2