ShorterMaps v1.2.0 ShorterMaps
{:shorter_maps, "~> 1.2"},
~M
sigil for map shorthand. ~M{a} ~> %{a: a}
New Features
v1.2
- Added support for map update syntax (
~M{old_map|first_name last_name}
), instead of writing%{old_map|first_name: first_name, last_name: last_name}
. Works with both~m
and~M
.
v1.1
- Added support for leading underscore variables (
~M{_id name} = person
), which allows specifying structural requirements while minimizing compiler warnings for unused variables.
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 (ES2015 for those folks who insist on proper names) 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.
Credits
ShorterMaps adds additional features to the original project, ShortMaps
, located here. The reasons for the divergence are summarized here.
The key syntactic difference is motivated by the trailing a
in ~m{}a
. To maintain backward compatibility, that syntax still works, but ShorterMaps adds a ~M sigil that defaults to the a
modifier.
Basic Usage
Note: you must import ShorterMaps
for the sigil to work.
Pattern Matching / Function Heads
iex> import ShorterMaps
...> ~M{foo bar baz} = %{foo: 1, bar: 2, baz: 3}
...> foo
1
...>
...> defmodule MyMod do
...> def extract_id(~M{id} = args), do: id
...> end
...> MyMod.extract_id(%{id: 5, name: "Chris"})
5
end
Map Construction
iex> import ShorterMaps
...> name = "Meg"
...> ~M{name} # M = atom keys
%{name: "Meg"}
...> ~m{name} # m = String keys
%{"name" => "Meg"}
Structs
The first word inside the sigil must be ‘%’ followed by the module name:
iex> import ShorterMaps
...> defmodule MyStruct do
...> defstruct [id: 0, name: ""]
...> end
...>
...> # Struct construction:
...> id = 5
...> name = "Chris"
...> ~M{%MyStruct id name}
%MyStruct{id: 5, name: "Chris"}
...>
...> # Pattern Matching:
...> ~M{%MyStruct id} = %MyStruct{id: 1, name: "Bob"}
...> id
1
Variable Pinning
iex> import ShorterMaps
...> name = "Meg"
...> ~M{^name} = %{name: "Meg"}
%{name: "Meg"}
...> ~M{^name} = %{name: "Megan"}
** (MatchError) no match of right hand side value: %{name: "Megan"}
Variable Ignore
Useful for pattern matching against the structure of a map when you don’t need all of the variables.
iex> import ShorterMaps
...> ~M{_foo bar} = %{foo: "bar", bar: "foo"}
%{bar: "foo", foo: "bar"}
...> bar
"foo"
...> foo
** (CompileError) iex:4: undefined function foo/0
Map Update Syntax
iex> import ShorterMaps
...> record = %{first_name: "chris", last_name: "meyer", id: 1}
...> [first_name, last_name] = ["Chris", "Meyer"]
...> updated = ~M{record|first_name last_name}
%{first_name: "Chris", id: 1, last_name: "Meyer"}
You can see more examples in the docs for the sigil_M
/sigil_m
macros.
Installation
# mix.exs
defp deps do
[
{:shorter_maps, "~> 1.2"},
]
end
Summary
Macros
Returns a map with the given keys bound to variables with the same name
Returns a string keyed map with the given keys bound to variables of the same name
Macros
Returns a map with the given keys bound to variables with the same name.
This macro sigil is used to reduce boilerplate when writing pattern matches on maps that bind variables with the same name as the map keys. For example, given a map that looks like this:
my_map = %{foo: "foo", bar: "bar", baz: "baz"}
..the following is very common Elixir code:
%{foo: foo, bar: bar, baz: baz} = my_map
foo #=> "foo"
The ~M
sigil provides a shorter way to do exactly this. It splits the given
list of words on whitespace (i.e., like the ~w
sigil) and creates a map with
these keys as the keys and with variables with the same name as values. Using
this sigil, this code can be reduced to just this:
~M{foo bar baz} = my_map
foo #=> "foo"
~M
can be used in regular pattern matches like the ones in the examples
above but also inside function heads (note the use of _bar
in this example):
defmodule Test do
import ShortMaps
def test(~M{foo _bar}), do: {:with_bar, foo}
def test(~M{foo}), do: foo
def test(_), do: :no_match
end
Test.test %{foo: "hello world", bar: :ok} #=> {:with_bar, "hello world"}
Test.test %{foo: "hello world"} #=> "hello world"
Test.test %{bar: "hey there!"} #=> :no_match
Pinning
Matching using the ~M
/~m
sigils has full support for the pin operator:
bar = "bar"
~M(foo ^bar) = %{foo: "foo", bar: "bar"} #=> this is ok, `bar` matches
foo #=> "foo"
bar #=> "bar"
~M(foo ^bar) = %{foo: "FOO", bar: "bar"} #=> this is still ok
foo #=> "FOO"; since we didn't pin it, it's now bound to a new value
bar #=> "bar"
~M(foo ^bar) = %{foo: "foo", bar: "BAR"} #=> will raise MatchError
Structs
For using structs instead of plain maps, the first word must be prefixed with ‘%’:
defmodule Foo do
defstruct bar: nil
end
~M(%Foo bar) = %Foo{bar: 4711}
bar #=> 4711
Modifiers
The ~m
and ~M
sigils support postfix operators for backwards
compatibility with ShortMaps
. Atom keys can be specified using the a
modifier, while string keys can be specified with the s
modifier.
~m(blah)a == ~M{blah}
~M(blah)s == ~m{blah}
Pitfalls
Interpolation isn’t supported. ~M(#{foo})
will raise an ArgumentError
exception.
The variables associated with the keys in the map have to exist in the scope
if the ~M
sigil is used outside a pattern match:
foo = "foo"
~M(foo bar) #=> ** (RuntimeError) undefined function: bar/0
Returns a string keyed map with the given keys bound to variables of the same name.
A common use of ~m
is when working with JSON, which uses exclusively string
keys for its maps. This macro can be used to construct maps from existing
variables, or to destructure a map into new variables:
# Construction:
name = "Chris"
id = 5
~m{name id} # <= %{"name" => "Chris", "id" => 5}
# Pattern Matching
~m{name} = %{"name" => "Bob", "id" => 3}
name # <= "Bob"
See ~M (sigil_M) and the README for extended usages.