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

【TIPS】構造体(struct)に使える関数/使えない関数(エラー出ないけど挙動がデタラメな関数も)

Last updated at Posted at 2024-01-01

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


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

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

構造体は、内部実装がマップなのに、マップ操作が一部できないですが、何ができて、何ができないかを確認します

Mapモジュール

値の更新 … 問題無し

なお、Map.replaceMap.intersectMap.merge 等も同様です

iex> 1..10//2 |> Map.put(:first, 2)
2..10//2
iex> 1..10//2 |> Map.put(:first, 2) |> Enum.to_list
[2, 4, 6, 8, 10]

構造体に無いキー/バリューの追加 … 無視される

構築時と同様です

iex> 1..10//2 |> Map.put(:no_exist, "I'm not Range")
2..10//2

値の削除 … NG

レンジは、inspect でエラーが出ていました

なお、Map.drop だけで無く、Map.deleteMap.popMap.rejectMap.split も同様です

iex> 1..10//2 |> Map.drop([:first])
#Inspect.Error<
  got FunctionClauseError with message:

      """
      no function clause matching in Inspect.Range.inspect/2
      """

  while inspecting:

      %{__struct__: Range, last: 10, step: 2}

  Stacktrace:

NaiveDateTimeでもエラーです … 構築時は雑で良かったのに、削除時は厳しいですね

iex> NaiveDateTime.utc_now() |> Map.drop([:microsecond])
NaiveDateTime.utc_now() |> Map.drop([:microsecond]) 
#Inspect.Error<
  got MatchError with message:

      """
      no match of right hand side value: %{__struct__: NaiveDateTime, calendar: Calendar.ISO, day: 1, hour: 10, minute: 19, month: 1, second: 26, year: 2024}
      """

  while inspecting:

      %{
        __struct__: NaiveDateTime,
        calendar: Calendar.ISO,
        day: 1,
        hour: 10,
        minute: 19,
        month: 1,
        second: 26,
        year: 2024
      }

  Stacktrace:

ということで、マップへの変換と構造体の再構築とセットにするなら、いけます

iex> 1..10//2 |> Map.from_struct |> Map.drop([:first]) |> then(& struct(Range, &1))      
nil..10//2

iex> NaiveDateTime.utc_now() |> Map.from_struct |> Map.drop([:microsecond]) |> then(& struct(NaiveDateTime, &1))
~N[2024-01-01 10:21:26]

リスト化 … 問題無し

iex> 1..10//2 |> Map.to_list        
[__struct__: Range, first: 1, last: 10, step: 2]

キー取得 … 問題無し

構造体用キーも含まれるので、除外を忘れずに

iex> 1..10//2 |> Map.keys 
[:__struct__, :first, :last, :step]

iex> 1..10//2 |> Map.keys |> List.delete(:__struct__)  
[:first, :last, :step]

値取得 … 問題無し

構造体名も含まれるので、除外を忘れずに

iex> 1..10//2 |> Map.values 
[Range, 1, 10, 2]

iex> 1..10//2 |> Map.values |> List.delete(Range)
[1, 10, 2]

Enumモジュール … 動かない or 動くがデタラメ

カウント … 動くがデタラメ

レンジのカウントは、4が期待値なので、エラーは出ないけど使わない方が良さそうです

iex> 1..10//2 |> Enum.count
5

NaiveDateTime は、Enumerable では無いため、全面的に Enum 利用がNGっぽいです(以降、省略)

iex> NaiveDateTime.utc_now() |> Enum.count
** (Protocol.UndefinedError) protocol Enumerable not implemented for ~N[2024-01-01 10:41:27.025257] of type NaiveDateTime (a struct). This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Stream
    (elixir 1.16.0) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.16.0) lib/enum.ex:178: Enumerable.count/1
    (elixir 1.16.0) lib/enum.ex:702: Enum.count/1
    iex:172: (file)

値の取得/更新 … 動くがマップ時と挙動が異なる

取得の時点でおかしいので、更新がそもそも出来ません

これはかなり厄介で、動かずエラーの方がマシなのに、中途半端に動いてしまうから、要注意です

iex> 1..10//2 |> Map.from_struct |> Enum.map(& &1)
[first: 1, last: 10, step: 2]

iex> 1..10//2 |> Enum.map(& &1)
[1, 3, 5, 7, 9]

値の削除 … 動くがマップ時と挙動が異なる

取得の時点でおかしいので、削除も出来ません

これもエラーが出ないけど挙動がデタラメなので、かなり厄介です

iex> 1..10//2 |> Map.from_struct |> Enum.drop(1)
[last: 10, step: 2]

iex> 1..10//2 |> Enum.drop(1)                   
[3, 5, 7, 9]
6
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
6
1