ETSMap v0.0.1 ETSMap

ETSMap is a Map-like Elixir data structure that is backed by an ETS table.

If you are not familiar with ETS, you should first familiarize yourself with it before using this module. This is not a drop-in replacement for the Map module. There are many critically important differences between a regular Map and an ETSMap.

That being said, being able to use the Elixir Map API with an ETS table is a nice convenience. If necessary, you can always use the normal ETS API by retrieving the underlying ETS table via the ETSMap struct field table.

Access

ETSMap supports the Access protocol, so you can use the access syntax:

iex> ets_map[:key]
:value

as well as the *_in family of functions from Kernel:

iex> put_in ets_map[:key], :value
#ETSMap<table: ..., [key: :value]>

Enumerable

ETSMap supports the Enumerable protocol, so all of the functions in Enum and Stream work as expected, as well as for comprehensions:

iex> Enum.map ets_map, fn {key, value} ->
  {key, value + 1}
end
[b: 3, a: 2]

iex> for {key, value} <- ets_map, do: {key, value + 1}
[b: 3, a: 2]

Collectable

ETSMap also supports the Collectable protocol, so Enum.into and Stream.into will function as expected, as well:

iex> ets_map = [a: 1, b: 2] |> Enum.into(ETSMap.new)
#ETSMap<table: ..., [b: 2, a: 1]>

iex> [c: 3, d: 4] |> Enum.into(ets_map)
#ETSMap<table: ..., [d: 4, c: 3, b: 2, a: 1]>

iex> for {k, v} <- ets_map, into: ets_map, do: {k, v + 1}
#ETSMap<table: ..., [d: 5, c: 4, b: 3, a: 2]>

iex> ets_map
#ETSMap<table: ..., [d: 5, c: 4, b: 3, a: 2]>

ETSMap vs. Map Semantics

This section should not be considered an exhaustive specification of the behavior of ETS (which you can find in the official ETS docs that you should have read already). The intention, instead, is to cover some of the largest differences that this behavior manifests between Maps and ETSMaps.

One of the important differences between a Map and an ETSMap is that ETSMaps are not immutable. Thus, any functions which would normally return a new (modified) copy of the input value will actually mutate all references to that value. However, this has the advantage of allowing the ETSMap/table to be written and read from multiple processes concurrently.

ETS tables are linked to their owner. By default, a table's owner is the process that created it. If the process that created it dies, the table will be deleted, even if references to it still exist in other processes. If no references to a table still exist, and the table's owner still exists, the table will be leaked. Ultimately ETSMaps are backed by ETS tables, so this behavior is important to keep in mind.

Summary

Functions

Deletes an ETSMap using :ets.delete

Deletes the entries for a specific key

Drops the given keys from the map

Checks if two ETSMaps are equal

Fetches the value for a specific key and returns it in a tuple

Fetches the value for specific key

Gets the value for a specific key

Gets the value from key and updates it, all in one pass

Gets the value from key and updates it. Raises if there is no key

Gets the value for a specific key

Returns whether a given key exists

Returns all keys

Merges two maps into one

Merges two maps into one

Returns a new ETSMap

Returns and removes all values associated with key

Lazily returns and removes all values associated with key in the map

Puts the given value under key

Puts the given value under key unless the entry key already exists

Evaluates fun and puts the result under key in map unless key is already present. This is useful if the value is very expensive to calculate or generally difficult to setup and teardown again

Takes all entries corresponding to the given keys and extracts them into a separate ETSMap

Takes all entries corresponding to the given keys and returns them in a new ETSMap

Converts the ETSMap to a list

Updates the key in map with the given function

Updates the key with the given function

Returns all values

Types

key :: any
new_opt ::
  {:enumerable, Enum.t} |
  {:transform, ({key, value} -> {key, value})} |
  {:name, atom} |
  {:ets_opts, Keyword.t}
t
value :: any

Functions

delete(map)

Specs

delete(t) :: :ok

Deletes an ETSMap using :ets.delete.

delete(map, key)

Specs

delete(t, key) :: t

Deletes the entries for a specific key.

If the key does not exist, does nothing.

Examples

iex> ETSMap.delete(ETSMap.new(%{a: 1, b: 2}), :a)
#ETSMap<table: ..., [b: 2]>
iex> ETSMap.delete(ETSMap.new(%{b: 2}), :a)
#ETSMap<table: ..., [b: 2]>
drop(map, keys)

Specs

drop(t, [key]) :: t

Drops the given keys from the map.

Examples

iex> ETSMap.drop(ETSMap.new(%{a: 1, b: 2, c: 3}), [:b, :d])
#ETSMap<table: ..., [a: 1, c: 3]>
equal?(map1, map2)

Specs

equal?(t, t) :: boolean

Checks if two ETSMaps are equal.

Two maps are considered to be equal if they contain the same keys and those keys contain the same values.

Examples

iex> ETSMap.equal?(ETSMap.new(%{a: 1, b: 2}), ETSMap.new(%{b: 2, a: 1}))
true
iex> ETSMap.equal?(ETSMap.new(%{a: 1, b: 2}), ETSMap.new(%{b: 1, a: 2}))
false
fetch(map, key)

Specs

fetch(t, key) :: {:ok, value} | :error

Fetches the value for a specific key and returns it in a tuple.

If the key does not exist, returns :error.

Examples

iex> ETSMap.fetch(ETSMap.new(%{a: 1}), :a)
{:ok, 1}
iex> ETSMap.fetch(ETSMap.new(%{a: 1}), :b)
:error
fetch!(map, key)

Specs

fetch!(t, key) :: value | no_return

Fetches the value for specific key.

If key does not exist, a KeyError is raised.

Examples

iex> ETSMap.fetch!(ETSMap.new(%{a: 1}), :a)
1
iex> ETSMap.fetch!(ETSMap.new(%{a: 1}), :b)
** (KeyError) key :b not found in: #ETSMap<table: ..., [a: 1]>
get(map, key, default \\ nil)

Specs

get(t, key, value) :: value

Gets the value for a specific key.

If key does not exist, return the default value (nil if no default value).

Examples

iex> ETSMap.get(ETSMap.new(%{}), :a)
nil
iex> ETSMap.get(ETSMap.new(%{a: 1}), :a)
1
iex> ETSMap.get(ETSMap.new(%{a: 1}), :b)
nil
iex> ETSMap.get(ETSMap.new(%{a: 1}), :b, 3)
3
get_and_update(map, key, fun)

Specs

get_and_update(t, key, (value -> {get, value})) :: {get, t} when get: value

Gets the value from key and updates it, all in one pass.

This fun argument receives the value of key (or nil if key is not present) and must return a two-elements tuple: the "get" value (the retrieved value, which can be operated on before being returned) and the new value to be stored under key.

The returned value is a tuple with the "get" value returned by fun and a new map with the updated value under key.

Examples

iex> ETSMap.get_and_update(ETSMap.new(%{a: 1}), :a, fn current_value ->
...>   {current_value, "new value!"}
...> end)
{1, #ETSMap<table: ..., [a: "new value!"]>}
iex> ETSMap.get_and_update(ETSMap.new(%{a: 1}), :b, fn current_value ->
...>   {current_value, "new value!"}
...> end)
{nil, #ETSMap<table: ..., [b: "new value!", a: 1]>}
get_and_update!(map, key, fun)

Specs

get_and_update!(t, key, (value -> {get, value})) ::
  {get, t} |
  no_return when get: value

Gets the value from key and updates it. Raises if there is no key.

This fun argument receives the value of key and must return a two-elements tuple: the "get" value (the retrieved value, which can be operated on before being returned) and the new value to be stored under key.

The returned value is a tuple with the "get" value returned by fun and a new map with the updated value under key.

Examples

iex> ETSMap.get_and_update(ETSMap.new(%{a: 1}), :a, fn current_value ->
...>   {current_value, "new value!"}
...> end)
{1, #ETSMap<table: ..., [a: "new value!"]>}
iex> ETSMap.get_and_update(ETSMap.new(%{a: 1}), :b, fn current_value ->
...>   {current_value, "new value!"}
...> end)
** (KeyError) key :b not found
get_lazy(map, key, fun)

Specs

get_lazy(t, key, (() -> value)) :: value

Gets the value for a specific key.

If key does not exist, lazily evaluates fun and returns its result.

This is useful if the default value is very expensive to calculate or generally difficult to setup and teardown again.

Examples

iex> map = ETSMap.new(%{a: 1})
iex> fun = fn ->
...>   # some expensive operation here
...>   13
...> end
iex> ETSMap.get_lazy(map, :a, fun)
1
iex> ETSMap.get_lazy(map, :b, fun)
13
has_key?(map, key)

Specs

has_key?(t, key) :: boolean

Returns whether a given key exists.

Examples

iex> ETSMap.has_key?(ETSMap.new(%{a: 1}), :a)
true
iex> ETSMap.has_key?(ETSMap.new(%{a: 1}), :b)
false
keys(map)

Specs

keys(t) :: [key]

Returns all keys.

Examples

iex> ETSMap.keys(ETSMap.new(%{a: 1, b: 2}))
[:a, :b]
merge(map1, map2)

Specs

merge(Enum.t, Enum.t) :: Enum.t

Merges two maps into one.

All keys in map2 will be added to map1, overriding any existing one.

Examples

iex> ETSMap.merge(ETSMap.new(%{a: 1, b: 2}), ETSMap.new(%{a: 3, d: 4}))
#ETSMap<table: ..., [d: 4, b: 2, a: 3]>
iex> ETSMap.merge(ETSMap.new(%{a: 1, b: 2}), %{a: 3, d: 4})
#ETSMap<table: ..., [d: 4, b: 2, a: 3]>
merge(map1, map2, callback)

Specs

merge(Enum.t, Enum.t, (key, value, value -> value)) :: map

Merges two maps into one.

All keys in map2 will be added to map1. The given function will be invoked with the key, value1 and value2 to solve conflicts.

Examples

iex> ETSMap.merge(ETSMap.new(%{a: 1, b: 2}), %{a: 3, d: 4}, fn _k, v1, v2 ->
...>   v1 + v2
...> end)
#ETSMap<table: ..., [a: 4, b: 2, d: 4]>
new(opts \\ [])

Specs

new(map | new_opts) :: t

Returns a new ETSMap.

If the appropriate options are provided, will call :ets.new to create table name using options ets_opts and then insert enumerable, using transform to transform the elements.

By default, name is set to :ets_map_table and ets_opts is set to [:set, :public]. The only supported table types are :set and :ordered_set.

There is also a convenience clause provided which takes a single argument (a map) which is inserted into the new ETSMap

Examples

iex> ETSMap.new(enumerable: %{a: 1, b: 2}, transform: fn {k, v} -> {k, v + 1} end)
#ETSMap<table: ..., [b: 3, a: 2]>
iex> ETSMap.new(%{a: 1})
#ETSMap<table: ..., [a: 2]>
pop(map, key, default \\ nil)

Specs

pop(t, key, value) :: {value, t}

Returns and removes all values associated with key.

Examples

iex> ETSMap.pop(ETSMap.new(%{a: 1}), :a)
{1, #ETSMap<table: ..., []>}
iex> ETSMap.pop(%{a: 1}, :b)
{nil, #ETSMap<table: ..., [a: 1]>}
iex> ETSMap.pop(%{a: 1}, :b, 3)
{3, #ETSMap<table: ..., [a: 1]>}
pop_lazy(map, key, fun)

Specs

pop_lazy(t, key, (() -> value)) :: {value, t}

Lazily returns and removes all values associated with key in the map.

This is useful if the default value is very expensive to calculate or generally difficult to setup and teardown again.

Examples

iex> map = ETSMap.new(%{a: 1})
iex> fun = fn ->
...>   # some expensive operation here
...>   13
...> end
iex> ETSMap.pop_lazy(map, :a, fun)
{1, #ETSMap<table: ..., []>}
iex> ETSMap.pop_lazy(map, :a, fun)
{13, #ETSMap<table: ..., []>}
put(map, key, value)

Specs

put(map, key, value) :: map

Puts the given value under key.

Examples

iex> ETSMap.put(ETSMap.new(%{a: 1}), :b, 2)
#ETSMap<table: ..., [a: 1, b: 2]>
iex> ETSMap.put(ETSMap.new(%{a: 1, b: 2}), :a, 3)
#ETSMap<table: ..., [a: 3, b: 2]>
put_new(map, key, value)

Specs

put_new(t, key, value) :: t

Puts the given value under key unless the entry key already exists.

Examples

iex> ETSMap.put_new(ETSMap.new(%{a: 1}), :b, 2)
#ETSMap<table: ..., [b: 2, a: 1]>
iex> ETSMap.put_new(ETSMap.new(%{a: 1, b: 2}), :a, 3)
#ETSMap<table: ..., [a: 1, b: 2]>
put_new_lazy(map, key, fun)

Specs

put_new_lazy(t, key, (() -> value)) :: map

Evaluates fun and puts the result under key in map unless key is already present. This is useful if the value is very expensive to calculate or generally difficult to setup and teardown again.

Examples

iex> map = ETSMap.new(%{a: 1})
iex> fun = fn ->
...>   # some expensive operation here
...>   3
...> end
iex> ETSMap.put_new_lazy(map, :a, fun)
#ETSMap<table: ..., [a: 1]>
iex> ETSMap.put_new_lazy(map, :b, fun)
#ETSMap<table: ..., [a: 1, b: 3]>
split(map, keys, new_opts \\ [])

Specs

split(t, [key], new_opts) :: t

Takes all entries corresponding to the given keys and extracts them into a separate ETSMap.

Returns a tuple with the new map and the old map with removed keys.

Keys for which there are no entires in the map are ignored.

Examples

iex> ETSMap.split(ETSMap.new(%{a: 1, b: 2, c: 3}), [:a, :c, :e])
{#ETSMap<table: ..., [a: 1, c: 3]>, #ETSMap<table: ..., [b: 2]>}
take(map, keys, new_opts \\ [])

Specs

take(t, [key], Keyword.t) :: t

Takes all entries corresponding to the given keys and returns them in a new ETSMap.

Examples

iex> ETSMap.take(ETSMap.new(%{a: 1, b: 2, c: 3}), [:a, :c, :e])
#ETSMap<table: ..., [a: 1, c: 3]>
to_list(map)

Specs

to_list(t) :: [{key, value}]

Converts the ETSMap to a list.

Examples

iex> ETSMap.to_list(ETSMap.new([a: 1]))
[a: 1]
iex> ETSMap.to_list(ETSMap.new(%{1 => 2}))
[{1, 2}]
update(map, key, initial, fun)

Specs

update(t, key, value, (value -> value)) :: t

Updates the key in map with the given function.

If the key does not exist, inserts the given initial value.

Examples

iex> ETSMap.update(ETSMap.new(%{a: 1}), :a, 13, &(&1 * 2))
#ETSMap<table: ..., [a: 2]>
iex> ETSMap.update(ETSMap.new(%{a: 1}), :b, 11, &(&1 * 2))
#ETSMap<table: ..., [a: 1, b: 11]>
update!(map, key, fun)

Specs

update!(map, key, (value -> value)) ::
  map |
  no_return

Updates the key with the given function.

If the key does not exist, raises KeyError.

Examples

iex> ETSMap.update!(ETSMap.new(%{a: 1}), :a, &(&1 * 2))
#ETSMap<table: ..., [a: 2]>
iex> ETSMap.update!(ETSMap.new(%{a: 1}), :b, &(&1 * 2))
** (KeyError) key :b not found
values(map)

Specs

values(t) :: [value]

Returns all values.

Examples

iex> ETSMap.values(ETSMap.new(%{a: 1, b: 2}))
[1, 2]