PatternMetonyms.pattern
pattern
, go back to PatternMetonyms module for more information.
Macro used to define pattern metonyms.
There are three types of pattern, listed below with examples:
- Implicitly Bidirectional
The simplest type of pattern, because of its symmetry requirement, it can only
be defined using concrete data (or adapted macro). Therefore no computation is
allowed, and they are thus compatible with case
and function heads.
They take the form:
pattern <name>(<[variables]>) = <pattern>
where pattern reuses the variables
iex> defmodule DoctestTPX do
...> import PatternMetonyms
...>
...> pattern ok(x) = {:ok, x}
...> pattern error(x) = {:error, x}
...>
...> pattern cons(x, xs) = [x | xs]
...>
...> def foo(x) do
...> view x do
...> ok(a) -> a
...> error(b) -> b
...> end
...> end
...>
...> def bar(x) do
...> case x do
...> ok(a) -> a
...> error(b) -> b
...> end
...> end
...>
...> def baz(ok(a) ), do: a
...> def baz(error(b)), do: b
...>
...> def mk_ok(x), do: ok(x)
...>
...> def blorg(xs) do
...> view xs do
...> cons(x, xs) -> cons(x, Enum.map(xs, fn x -> -x end))
...> end
...> end
...> end
iex> DoctestTPX.foo({:ok, :banana})
:banana
iex> DoctestTPX.foo({:error, :split})
:split
iex> DoctestTPX.bar({:ok, :peach})
:peach
iex> DoctestTPX.baz({:error, :melba})
:melba
iex> DoctestTPX.mk_ok(:melba)
{:ok, :melba}
iex> DoctestTPX.blorg([1, 2, 3])
[1, -2, -3]
- Unidirectional
This type of pattern is read only, it may be used as abstraction over pattern matching on concrete data type
that can not be reused to construct data, or as abstraction over views, as explained in view/2
.
They take the form:
pattern <name>(<[variables]>) <- <pattern>
pattern <name>(<[variables]>) <- (<function> -> <pattern>)
where pattern reuses the variables.
(function -> pattern)
is called a view
iex> defmodule DoctestTPY do
...> import PatternMetonyms
...>
...> pattern head(x) <- [x | _]
...>
...> pattern rev_head(x) <- (reverse() -> head(x))
...>
...> def reverse(xs), do: Enum.reverse(xs)
...>
...> def foo(xs) do
...> view xs do
...> head(x) -> x
...> [] -> []
...> end
...> end
...>
...> def bar(x) do
...> case x do
...> head(a) -> a
...> [] -> []
...> end
...> end
...>
...> def baz(head(a)), do: a
...>
...> def blorg(xs) do
...> view xs do
...> rev_head(x) -> x
...> end
...> end
...> end
iex> DoctestTPY.foo([1, 2, 3])
1
iex> DoctestTPY.bar([1, 2, 3])
1
iex> DoctestTPY.baz([1, 2, 3])
1
iex> DoctestTPY.blorg([1, 2, 3])
3
- Explicitly bidirectional
This type of pattern allows the same kind of abstraction as unidirectional one, but also permit defining how to construct data from computation (if necessary). They take the form:
pattern (<name>(<[variables]>) <- (<function> -> <pattern>)) when <name>(<[variables]>) = <builder>
where pattern and builder reuse the variables.
(function -> pattern)
is called a view
iex> defmodule DoctestTPZ do
...> import PatternMetonyms
...>
...> pattern (snoc(x, xs) <- (unsnoc() -> {x, xs}))
...> when snoc(x, xs) = Enum.reverse([x | Enum.reverse(xs)])
...>
...> defp unsnoc([]), do: :error
...> defp unsnoc(xs) do
...> [x | rev_tail] = Enum.reverse(xs)
...> {x, Enum.reverse(rev_tail)}
...> end
...>
...> def foo(xs) do
...> view xs do
...> snoc(x, _) -> x
...> [] -> []
...> end
...> end
...>
...> def bar(xs) do
...> view xs do
...> snoc(x, xs) -> snoc(-x, xs)
...> [] -> []
...> end
...> end
...> end
iex> DoctestTPZ.foo([1, 2, 3])
3
iex> DoctestTPZ.bar([1, 2, 3])
[1, 2, -3]
Patterns using a view can not be used with case
.
Remote function can be used within a view, but the __MODULE__
alias won't work
because the expansion is not done at the usage site. It is not yet determined
which behavior is desired.
iex> defmodule DoctestTPA do
...> import PatternMetonyms
...>
...> pattern rev_head(x) <- (Enum.reverse -> [x | _])
...>
...> def blorg(xs) do
...> view xs do
...> rev_head(x) -> x
...> end
...> end
...> end
iex> DoctestTPA.blorg([1, 2, 3])
3
Unknown yet if anonymous functions can be supported.
Guards within a pattern definition is considered undefined behavior, it may work, but it depends on the context. Consider that if the behavior gets a specification, it would be the removal of the possibility of using them. Patterns using a view pattern are the recommend approach. For example:
pattern heart(n) <- (less_than_3 -> {:ok, n})
Patterns can be documented:
@doc """
heart matches when the number is heartfelt <3
"""
pattern heart(n) <- (less_than_3() -> {:ok, n})
You can then access the doc as usual: h heart
, or h Module.heart
.