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
Functions
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]>
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]>
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
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
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]>
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
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]>}
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
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
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
Returns all keys.
Examples
iex> ETSMap.keys(ETSMap.new(%{a: 1, b: 2}))
[:a, :b]
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]>
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]>
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]>
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]>}
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: ..., []>}
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]>
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]>
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]>
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]>}
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]>
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}]
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]>
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