115

In elixir we have Maps:

> map = %{:a => "one", :b => "two"} # = %{a: "one", b: "two"}
> map.a                             # = "one"
> map[:a]                           # = "one"

We also have Keyword Lists:

> 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)

Why both?

Syntax? Is it because Keyword Lists have a more flexible syntax allowing them to be defined without curlys and even without brackets as the last param of a function call? Then why not give Maps this syntactic sugar?

Duplicate Keys? Is it because Keyword Lists can have duplicate keys? Why would you want both Map style access and duplicate keys?

Performance? Is it because Keyword Lists have better performance? Then why have Maps? And shouldn't maps be more performant at looking up members by key than a list of tuples?

JS Array and Ruby Hash like appearance? Is that it?

I understand that structurally they are different data representations. To me it seems that Keyword Lists in elixir serve to complicate the language through exceptional syntax (3 different syntactic variants), use case overlap with maps, and an unclear benefit.

What is the benefit of using Keyword Lists?

greggreg
  • 11,945
  • 6
  • 37
  • 52

3 Answers3

153
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⁴
Performance¹ (Access) slow⁵ fast³ fast⁴

Keyword lists are lightweight and have a simple structure underneath them, which makes them very flexible. You can think of them as syntax sugar on top of an Erlang convention, making it easy to interface with Erlang without writing too ugly code. For example, keyword lists are used to represent function arguments, which is a property inherited from Erlang. In some cases, keyword lists are your only choice, especially if you need duplicate keys or ordering. They simply have different properties than the other alternatives, which make them more suitable for some situations and less for others.

Maps (and Structs) are used to store actual payload data, since they have a hash-based implementation. Keyword lists internally are just lists that need to be traversed for each operation, so they don't have the properties of classic key-value data structures like constant time access.

Elixir also introduced HashDict as a workaround for the poor performance of maps at the time it was written. However, this is fixed now as of Elixir 1.0.5/Erlang 18.0 and HashDict will be deprecated in future versions.

If you dig deeper into the Erlang standard library, there are even more data structures that store key/value pairs:

  • proplists – similar to Elixir keyword lists
  • maps – same as Elixir maps
  • dict – key-value dictionaries built from Erlang primitives
  • gb_trees – general balanced tree

You also have these options when you need to store key/value pairs across multiple processes and/or VMs:

  • ets/dets – (disk based) Erlang term storage
  • mnesia – distributed database

¹ Generally speaking, but of course it depends™.

² Best case is just prepending to a list.

³ Applies to Elixir 1.0.5 and above, may be slower in older versions.

HashDict is now being deprecated.

⁵ Requires a linear search which on average scans half of the elements.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • 1
    Allowing duplicate keys and being ordered aren't benefits, but different properties. You need to pick the data structure that fits your needs. –  Jan 27 '15 at 22:50
  • 2
    Strictly speaking, yes, but they might turn out to be benefits if you need those properties — that's what I meant. – Patrick Oscity Jan 27 '15 at 22:53
  • @PatrickOscity: In such a case, surely they would be better categorised as _requirements_? – Lightness Races in Orbit Jan 27 '15 at 23:09
  • I've tried to formulate my answer better. You're all welcome to suggest edits if you think something is wrong or can be expressed in a better way! – Patrick Oscity Jan 28 '15 at 06:13
  • Thanks for the rundown! Even without the concept of Keyword Lists it would still be possible to construct a List of Tuples with an Atom at position 0. So all the benefit from ordering and multiple keys is kind of moot. Ultimately I think Keyword Lists being a "type" in elixir is confusing and hams up the syntax but to each his own. I can't help but think that their existence as a special type boils down to one word: erlang. – greggreg Jan 28 '15 at 20:24
  • Its easy to think of kw-lists as beimg just syntactic sugar. On the other hand, the existence of a proplist Erlang module and the implemented Dict behavior make it a type. Not a type in a narrow syntactic sense, but in a wider sense, meaning a set of values and behaviors. The wiki article on data types is worth a read, especially the definitions of what a type is: http://en.m.wikipedia.org/wiki/Data_type – Patrick Oscity Jan 28 '15 at 21:15
  • 13
    @greggreg There is another implicit benefit of having keywords lists: we make a distinction about structured and non-structured data. Maps are *extremely* useful for structured data with a known set of keys and keywords are not. Today, most of the maps usage are for structured data and we leave keywords for optional ones. If we only had maps I think a good part of this distinction would be lost. – José Valim Jan 29 '15 at 08:59
  • I also hope that Erlang 18 will introduce large maps and allow us to get rid of HashDict in the long term (crosses fingers). – José Valim Jan 29 '15 at 09:00
  • 1
    In fact it did, maps are the way to go from erlang 18. – Papipo Jul 11 '15 at 22:08
  • Just note that keword lists and proplists are not exactly the same. A proplist will allow a single atom, eg `[{:foo, 7}, :bar, {:baz, :biz}]`, whereas in a keyword list you need to use a 2-tuple, eg `[{:foo, 7}, {:bar, :true}, {:baz, :biz}]` – jisaacstone May 16 '16 at 19:52
  • @jisaacstone thanks for pointing this out, edited my answer to say "similar to" instead of "same as" – Patrick Oscity May 17 '16 at 06:33
  • I wonder why elixir does not implement this feature using maps as maps are more suitable for optional parameters and do not care about orders thus are more friendly to pattern matching. – Aetherus Dec 25 '16 at 09:04
  • Another point to add is that Keyword Lists expect the key to be an atom, whereas Erlang proplists make no such assumption (except that a single atom represents `{atom, true}` – Cody Poll Jun 13 '17 at 20:49
13

The main benefit of keyword lists is a backward compatibility with existing elixir and erlang codebase.

They also adds syntax sugar if used as functions arguments which resembles for e.g. a ruby syntax:

def some_fun(arg, opts \\ []), do: ...
some_fun arg, opt1: 1, opt2: 2

The main drawback of using keyword lists is that it's not possible to perform a partial pattern matching on them:

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]

Let's extend it to function arguments. Imagine we need to handle a multiclause function based on a value of one of the options:

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

This will never execute the do_special_thing:

fun1("arg", opt1: nil, opt2: "some value")
doing regular thing  

With map arguments it will work:

fun2("arg", %{opt1: nil, opt2: "some value"})
doing special thing
Voldy
  • 12,829
  • 8
  • 51
  • 67
3

Maps allow only one entry for a particular key, whereas keyword lists allow the key to be repeated. Maps are efficient (particularly as they grow), and they can be used in Elixir’s pattern matching.

In general, use keyword lists for things such as command-line parameters and for passing around options, and use maps (or another data structure, the HashDict ) when you want an associative array.

Subhash Chandra
  • 3,165
  • 1
  • 27
  • 30