Matcha (Matcha v0.1.3) View Source
First-class match specification tooling for Elixir.
Matcha
offers tight integration with Elixir and match specifications.
Match specifications are a BEAM VM feature that executes simple pattern matching operations very close-to-the-metal, often several thousand times more performant than a comparable Enum
operation. They can be used to efficiently:
However, they are notoriously difficult to compose and use. Matcha makes this intuitive with ergonomic macros and a fluent API with which to manipulate them.
Examples
require Matcha
# Turns Elixir code into a match specification
iex> spec = Matcha.spec do
...> {x, y, z} -> x + y + z
...> end
...> spec.source
[{{:"$1", :"$2", :"$3"}, [], [{:+, {:+, :"$1", :"$2"}, :"$3"}]}]
For more information, check out the interactive usage guides, including using Matcha for:
Known Limitations
Currently, it is not possible to:
- Use the
Kernel.in/2
macro in guards. (see: open issue) - Use the
Kernel.tuple_size/1
or:erlang.tuple_size/1
guards. (see: documentation)- This is a fundamental limitation of match specs.
- Use any
is_record
guards (neither Elixir's implementation because of theKernel.tuple_size/1
limitation above, nor erlang's implementation for other reasons). (see: documentation) - Both destructure values from a data structure into bindings, and assign the datastructure to a variable, except at the top-level of a clause.
- This is how match specs work by design; though there may be a work-around using
:erlang.map_get/2
for maps, but at this time introducing an inconsistency doesn't seem worth it.
- This is how match specs work by design; though there may be a work-around using
Link to this section Summary
Functions
Builds a Matcha.Pattern
that represents a "filter" operation on a given input.
Builds a Matcha.Spec
that represents a "filter+map" operation on a given input.
Traces function
calls to module
, executing a spec
on matching arguments.
Link to this section Functions
Specs
Builds a Matcha.Pattern
that represents a "filter" operation on a given input.
Match patterns represent a "filter" operation on a given input, ignoring anything that does not fit the match "shape" specified.
For more information on match patterns, consult the Matcha.Pattern
docs.
Examples
iex> require Matcha
...> pattern = Matcha.pattern({x, y, x})
#Matcha.Pattern<{:"$1", :"$2", :"$1"}>
iex> Matcha.Pattern.match?(pattern, {1, 2, 3})
false
iex> Matcha.Pattern.match?(pattern, {1, 2, 1})
true
Specs
spec(Matcha.Context.t(), Macro.t()) :: Macro.t()
Builds a Matcha.Spec
that represents a "filter+map" operation on a given input.
The context
may be :filter_map
, :table
, :trace
, or a Matcha.Context
module.
This is detailed in the Matcha.Context
docs.
For more information on match specs, consult the Matcha.Spec
docs.
Examples
iex> require Matcha
...> Matcha.spec do
...> {x, y, x}
...> when x > y and y > 0
...> -> x
...> {x, y, y}
...> when x < y and y < 0
...> -> y
...> end
#Matcha.Spec<[{{:"$1", :"$2", :"$1"}, [{:andalso, {:>, :"$1", :"$2"}, {:>, :"$2", 0}}], [:"$1"]}, {{:"$1", :"$2", :"$2"}, [{:andalso, {:<, :"$1", :"$2"}, {:<, :"$2", 0}}], [:"$2"]}], context: :filter_map>
Traces function
calls to module
, executing a spec
on matching arguments.
Tracing is a powerful feature of the BEAM VM, allowing for near zero-cost
monitoring of what is happening in running systems.
The functions in Matcha.Trace
provide utilities for accessing this functionality.
One of the most powerful forms of tracing uses match specifications: rather that just print information on when a certain function signature with some number of arguments is invoked, they let you:
- dissect the arguments in question with pattern-matching and guards
- take special actions in response (documented in
Matcha.Context.Trace
)
This macro is a shortcut for constructing a spec
with the :trace
context via Matcha.spec/2
,
and tracing the specified module
and function
with it via Matcha.Trace.calls/4
.
For more information on tracing in general, consult the Matcha.Trace
docs.
Examples
iex> require Matcha
...> Matcha.trace_calls(Enum, :join, limit: 3) do
...> [_enumerable] -> message("using default joiner")
...> [_enumerable, ""] -> message("using default joiner (but explicitly)")
...> [_enumerable, _custom] -> message("using custom joiner")
...> end
...> Enum.join(1..3)
# Prints a trace message with "using default joiner" appended
"123"
iex> Enum.join(1..3, "")
# Prints a trace message with "using default joiner (but explicitly)" appended
"123"
iex> Enum.join(1..3, ", ")
# Prints a trace message with "using custom joiner" appended
"1, 2, 3"