エリクサーにはマップがあります:
> map = %{:a => "one", :b => "two"} # = %{a: "one", b: "two"}
> map.a # = "one"
> map[:a] # = "one"
キーワードリストもあります。
> kl = [a: "one", b: "two"] # = [a: "one", b: "two"]
> kl2 = [{:a, "one"},{:b, "two"}] # = [a: "one", b: "two"]
> kl == kl2 # = true
> kl[:a] # = "one"
> kl.a # = ** (ArgumentError)
なぜ両方?
構文?キーワードリストには、関数呼び出しの最後のパラメーターとしてcurlysを使用せず、かっこなしでも定義できるより柔軟な構文があるためですか?それでは、Mapsにこの構文糖衣を与えてみませんか?
重複キー?これは、キーワードリストに重複キーがある可能性があるためですか?マップスタイルのアクセスとキーの複製の両方が必要なのはなぜですか?
パフォーマンス?これは、キーワードリストのパフォーマンスが優れているためですか?では、なぜマップが必要なのでしょうか?また、マップは、タプルのリストよりも、キーでメンバーを検索する際のパフォーマンスが高くないはずですか?
JS配列とRuby外観のようなハッシュ?それですか?
構造的には異なるデータ表現であることを理解しています。私には、elixirのキーワードリストは、例外的な構文(3つの異なる構文バリアント)、マップとのユースケースの重複、および不明確な利点によって言語を複雑にしているようです。
キーワードリストを使用する利点は何ですか?
┌──────────────┬────────────┬───────────────────────┐
│ Keyword List │ Map/Struct │ HashDict (deprecated) │
┌──────────────────┼──────────────┼────────────┼───────────────────────┤
│ Duplicate keys │ yes │ no │ no │
│ Ordered │ yes │ no │ no │
│ Pattern matching │ yes │ yes │ no │
│ Performance¹ │ — │ — │ — │
│ ├ Insert │ very fast² │ fast³ │ fast⁴ │
│ └ Access │ slow⁵ │ fast³ │ fast⁴ │
└──────────────────┴──────────────┴────────────┴───────────────────────┘
キーワードリストは軽量で、その下に単純な構造があるため、非常に柔軟です。それらはErlang規約に基づく構文シュガーと考えることができ、tooいコードを書かずにErlangとのインターフェースを容易にします。たとえば、キーワードリストは、Erlangから継承されたプロパティである関数の引数を表すために使用されます。場合によっては、特に重複キーまたは順序付けが必要な場合は、キーワードリストのみが選択されます。それらは単に他の選択肢とは異なる特性を持っているため、ある状況にはより適し、他の状況にはあまり適していません。
マップ(および構造体)は、ハッシュベースの実装を備えているため、実際のペイロードデータを格納するために使用されます。内部的にキーワードリストは、各操作でトラバースする必要がある単なるリストであるため、一定時間アクセスなどの従来のキー値データ構造のプロパティはありません。
Elixirは、 作成時のマップのパフォーマンスが低い の回避策としてHashDict
も導入しました。ただし、Elixir 1.0.5/Erlang 18.0およびHashDict
将来のバージョンでは非推奨 で修正されました。
Erlang標準ライブラリをさらに深く掘り下げると、キー/値のペアを格納するデータ構造がさらに増えます。
複数のプロセスやVMにキー/値のペアを保存する必要がある場合にも、次のオプションがあります。
¹一般的に言えば、もちろん依存します™。
²ベストケースは、リストの先頭に追加することです。
³Elixir 1.0.5以降に適用されますが、古いバージョンでは遅くなる場合があります。
_ HashDict
は非推奨になりました。
average平均で要素の半分をスキャンする線形検索が必要です。
キーワードリストの主な利点は、既存のelixirおよびerlangコードベースとの後方互換性です。
また、関数の引数として使用される場合、たとえばa Ruby構文:
def some_fun(arg, opts \\ []), do: ...
some_fun arg, opt1: 1, opt2: 2
キーワードリストを使用する主な欠点は、キーワードリストで部分的なパターンマッチングを実行できないことです。
iex(1)> m = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> %{a: a} = m
%{a: 1, b: 2}
iex(3)> a
1
iex(4)> k = [a: 1, b: 2]
[a: 1, b: 2]
iex(5)> [a: a] = k
** (MatchError) no match of right hand side value: [a: 1, b: 2]
関数の引数に拡張しましょう。次のオプションのいずれかの値に基づいて、複数節関数を処理する必要があると想像してください。
def fun1(arg, opt1: opt1) when is_nil(opt1), do: do_special_thing
def fun1(arg, opts), do: do_regular_thing
def fun2(arg, %{opt1: opt1}) when is_nil(opt1), do: do_special_thing
def fun2(arg, opts), do: do_regular_thing
これはdo_special_thing
を決して実行しません:
fun1("arg", opt1: nil, opt2: "some value")
doing regular thing
マップ引数を使用すると動作します:
fun2("arg", %{opt1: nil, opt2: "some value"})
doing special thing
マップでは特定のキーに対して1つのエントリのみが許可されますが、キーワードリストではキーを繰り返すことができます。マップは(特に成長するにつれて)効率的であり、Elixirのパターンマッチングで使用できます。
一般に、コマンドラインパラメーターやオプションの受け渡しなどにキーワードリストを使用し、連想配列が必要な場合はマップ(または別のデータ構造、HashDict)を使用します。