ShorterMaps v2.2.5 ShorterMaps

ShorterMaps

~M sigil for map shorthand. ~M{a} ~> %{a: a}

Build Status

Getting started

1) Add {:shorter_maps, "~> 2.0"}, to your mix deps 2) Add import ShorterMaps 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}.

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. ShorterMaps provides that functionality to Elixir.

Syntax:

~M and ~m can be used to replace maps anywhere in your code. The ShorterMaps 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 ShorterMaps
...> 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}

# 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

ShorterMaps adds 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}
  • 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 ShorterMaps for the sigils to work.

Summary

Macros

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}

Macros

sigil_M(term, modifiers)

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}
sigil_m(term, modifiers)

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.