TinyMaps (TinyMaps v3.1.0-rc.1)

TinyMaps

A successor to the shorter_maps package. Now with keyword lists!

~M sigil for map shorthand. ~M{a} ~> %{a: a} ~K sigil for keyword list shorthand. ~K{a} ~> [a: a]

Build Status

Getting started

  1. Add {:tiny_maps, "~> 3.0"}, to your mix deps
  2. Add import TinyMaps to the top of your module
  3. DRY up your maps and structs with ~M and ~m. Instead of %{name: name} use ~M{name}, and for %{"name" => name} use ~m{name}. When the key and the variable don't match, don't fret: ~M{name, id: current_id} expands to %{name: name, id: current_id}. Now use the ~K sigil to DRY up keyword lists.

Motivation

Code like %{id: id, name: name, address: address} occurs with high frequency in many programming languages. In Elixir, additional uses occur as we pattern match to destructure existing maps.

ES6 provided javascript with a shorthand to create maps with keys inferred by variable names, and allowed destructuring those maps into variables named for the keys. TinyMaps provides that functionality to Elixir.

Syntax:

~M, ~m, and ~K can be used to replace maps anywhere in your code. The TinyMaps sigil syntax operates just like a vanilla elixir map, with two main differences:

  1. When a variable name stands alone, it is replaced with a key-value pair, where the key is the variable name as a string (~m) or an atom (~M). The value will be the variable. For example, ~M{name, id: get_free_id()} expands to %{name: name, id: get_free_id()}.

  2. Struct names are enclosed in the sigil, rather than outside, e.g.: ~M{%StructName key, key2} === %StructName{key: key, key2: key2}. The struct name must be followed by a space, and then comma-separated keys. Structs can be updated just like maps: ~M{%StructName old_struct|key_to_update}

Examples

iex> import TinyMaps
...> name = "Chris"
...> id = 6
...> ~M{name, id}
%{name: "Chris", id: 6}

# It's ok to mix in other expressions:
...> ~M{name, id: id + 200}
%{name: "Chris", id: 206}

# or even nest the sigil (note the change in delimiters to paren):
...> ~M{name, id, extra_copy: ~M(name, id)}
%{name: "Chris", id: 6, extra_copy: %{name: "Chris", id: 6}}

# We can use String keys:
...> ~m{name, id}
%{"name" => "Chris", "id" => 6}

# And we can update existing maps:
...> map_1 = %{name: "Bob", id: 9}
...> ~M{map_1|name}
%{name: "Chris", id: 9}

# Struct syntax is a little funky:
...> defmodule MyStruct do
...>   defstruct [id: nil, name: :default]
...> end
...> ~M{%MyStruct id}
%MyStruct{id: 6, name: :default}

# Structs can be updated too:
...> initial_struct = %MyStruct{name: "Chris", id: :unknown}
...> ~M{%MyStruct initial_struct|id}
%MyStruct{name: "Chris", id: 6}

# Keyword lists can be built with the ~K sigil
...> a = 1
...> b = 2
...> ~K{a, b}
[a: 1, b: 2]

# And even nested
...> a = 1
...> b = 2
...> ~K{a, b: ~K(b)}
[a: 1, b: [b: 2]]

# Because the expansion happens at compile time, they can be used __anywhere__:

# in function heads:
...> defmodule MyModule do
...>   def my_func(~M{name, _id}), do: {:id_present, name}
...>   def my_func(~M{name}), do: {:no_id, name}
...> end

# in pattern matches:
...> ~M{age, model} = %{age: -30, model: "Delorean", manufacturer: "AMC"}
...> age
-30

Credits

TinyMaps is continuation of the ShorterMaps which has become unmaintained and I have not been able to make contact with the maintainer.

ShorterMaps added additional features to the original project, ShortMaps, located here. The reasons for the divergence are summarized here.

Quick Reference:

  • Atom keys: ~M{a, b} => %{a: a, b: b}
  • String keys: ~m{a, b} => %{"a" => a, "b" => b}
  • Keyword lists: ~K{a, b, c: d} => [a: a, b: b, c: d]
  • Structs: ~M{%Person id, name} => %Person{id: id, name: name}
  • Pinned variables: ~M{^a, b} => %{a: ^a, b: b}
  • Ignore matching: ~M{_a, b} => %{a: _a, b: b}
  • Map update (strings or atoms): ~M{old|a, b, c} => %{old|a: a, b: b, c: c}
  • Struct update: ~M{%Person old_struct|name} => %Person{old_struct|name: name}
  • Mixed mode: ~M{a, b: b_alt} => %{a: a, b: b_alt}
  • Expressions: ~M{a, b: a + 1} => %{a: a, b: a + 1}
  • Zero-arity: ~M{a, b()} => %{a: a, b: b()}
  • Modifiers: ~m{blah}a == ~M{blah} or ~M{blah}s == ~m{blah}

Note: you must import TinyMaps for the sigils to work.

Summary

Functions

Expands an atom-keyed keyword list with the given keys bound to variables with the same name.

Expands an atom-keyed map with the given keys bound to variables with the same name.

Expands to a string keyed map where the keys are a string containing the variable names, e.g. ~m{name} expands to %{"name" => name}.

Functions

Link to this macro

sigil_K(term, modifiers)

(macro)

Expands an atom-keyed keyword list with the given keys bound to variables with the same name.

Because ~K operates on atoms, it is compatible with Structs.

Examples:

# Keyword List construction:
iex> tty = "/dev/ttyUSB0"
...> baud = 19200
...> device = ~K{tty, baud}
[baud: 19200, tty: "/dev/ttyUSB0"]

# Keyword List with field
iex> id = 100
...> name = "John"
...> ~K{id, creator: name}
[id: 100, creator: "John"]
Link to this macro

sigil_M(term, modifiers)

(macro)

Expands an atom-keyed map with the given keys bound to variables with the same name.

Because ~M operates on atoms, it is compatible with Structs.

Examples:

# Map construction:
iex> tty = "/dev/ttyUSB0"
...> baud = 19200
...> device = ~M{tty, baud}
%{baud: 19200, tty: "/dev/ttyUSB0"}

# Map Update:
...> baud = 115200
...> %{device|baud}
%{baud: 115200, tty: "/dev/ttyUSB0"}

# Struct Construction
iex> id = 100
...> ~M{%Person id}
%Person{id: 100, other_key: :default_val}
Link to this macro

sigil_m(term, modifiers)

(macro)

Expands to a string keyed map where the keys are a string containing the variable names, e.g. ~m{name} expands to %{"name" => name}.

Some common uses of ~m are when working with JSON and Regex captures, which use exclusively string keys in their maps.

# JSON example:
# Here, `~m{name, age}` expands to `%{"name" => name, "age" => age}`
iex> ~m{name, age} = Poison.decode!("{"name": "Chris","age": "old"}")
%{"name" => "Chris", "age" => "old"}
...> name
"Chris"
...> age
"old"

See the README for extended syntax and usage.