Mix.install(
[
{:matcha, github: "christhekeele/matcha", tag: "stable"},
{:ex2ms, ">= 0.0.1"}
],
force: true
)
Matchspecs can be arcane, load-bearing spells at critical points in your codebase. If you are considering adopting Matcha
, here are some tips to help you transition.
This is an in-depth guide to adopting Matcha
in your projects. For a quick reference, see the adoption cheatsheet.
The first part of this guide is targeted at projects already using match specifications, that want to adopt Matcha
to compose them. If you simply want to plug your existing specs into Matcha
's APIs, or only use Matcha
for new match specs, you can skip to the "Choosing Matcha Contexts" section below.
SUMMARY
Matcha
will issue familiar Elixir compiler warnings for the Elixir code you give it- You can move from
ex2ms
toMatcha
by swappingEx2ms.fun
forMatcha.spec
- Both compilers may produce different but semantically equivalent specs
- You should specify the context you intend to use a spec in
Matcha.spec/2
produces aMatcha.Spec
struct wrapping raw Erlang specs- Use
Matcha.Spec.raw/1
to unwrap the struct before passing it to non-Matcha
functions- Use higher-level
Matcha.Table
andMatcha.Trace
APIs for a more native Elixir experience
Initially, you'll want to read your original spec to try to understand what it is doing, and convert it mentally into Elixir code. This may require learning the entire Erlang match spec grammar. Matcha does not currently have a syntax guide to Erlang's match specs, but we intend to develop one, and will replace this callout with a link to it and a walkthrough when it is launched.
If you are moving raw, handwritten specs to Matcha
, you can skip to the "Wrapping Raw Specs" section below.
If you are using another Elixir-to-MS compiler, such as ex2ms
, you already have Elixir code that describes what you are trying to do! Now, you must convince yourself that Matcha
will do the same thing with it. The simplest way to get started with this is to compare both compilers' output. We'll start by requiring both compiler's macros:
require Ex2ms
require Matcha
Now, let's take this example from the ex2ms
documentation:
ex2ms_spec =
Ex2ms.fun do
{x, y} = z when x > 10 -> z
end
To see what Matcha
compiles this to, we simply replace calls to Ex2ms.fun/1
with Matcha.spec/1
:
matcha_spec =
Matcha.spec :table do
{x, y} = z when x > 10 -> z
end
You'll notice a couple things running these examples.
Firstly, the Matcha
version emitted an Elixir compiler warning: variable "y" is unused
! This is a core feature of Matcha
: the Elixir code you give it is passed through the Elixir compiler to ensure that all useful warnings about your match specification code are preserved and emitted as expected. When moving over to Matcha
, you may find new opportunities to clean your match specification code up, and hold it to the same standard as your Elixir code.
Secondly, we do not get back raw Erlang match specification source code; instead, our spec is wrapped in a Matcha.Spec
struct. This lets it play nicely with high-level Matcha
APIs. If instead we want to access the raw specification source code, for example to compare it to ex2ms
output or pass it into an Erlang API, we can call Matcha.Spec.raw/1
:
Matcha.Spec.raw(matcha_spec)
At the time of writing, both of these compilers produce the same raw Erlang match spec for this example. This is not guaranteed, however鈥攖hey well may produce semantically equivalent, syntactically different match specifications as the compilers evolve.
If you want to see if they produce different results, ExUnit.Assertions.assert/1
can let us know if they are the same, or cleanly depict how they are different:
require ExUnit.Assertions
ExUnit.Assertions.assert(Matcha.Spec.raw(matcha_spec) == ex2ms_spec)
Via this mechanism, you can study the differences and similarities between the compilers and satisfy yourself that both tools produce similar, if not identical, match specifications.
If you want to convince yourself that these do the same thing, see "Reading Raw Specs" above. If you want the computer to convice you, continue reading "Testing Specs With Erlang APIs" below.
If you are not using the Macro.spec/1
macro to build your specs, you can still wrap existing ones for usage with Matcha
APIs: provide them to Matcha.Spec.from_raw!/2
to get a Matcha.Spec
struct. For example:
raw_spec = [{{:"$1", :"$2"}, [{:>, :"$1", 10}], [:"$_"]}]
matcha_spec = Matcha.Spec.from_raw!(:table, raw_spec)
At this point, you have Matcha.Spec
structs you want to use, either from:
- Using the
Matcha.spec/1
macro to build them - Using
Matcha.Spec.from_raw!/2
to wrap existing ones
We can access their raw match spec source with Matcha.Spec.raw/1
and pass them to Erlang APIs. If we want to use Matcha
APIs instead, however, we will want to provide them with a context, which both of the above functions accept as an optional parameter.
Erlang match specs aren't just a DSL with their own grammar, they actually describe several different grammars depending on how you intend to use the spec. Matcha
encodes this intention as a Matcha.Context
, and handles them differently at both compile-time and runtime to enforce different guarantees and support different use-cases.
The Matcha.Context
documentation goes into this in more depth, but for all practical purposes the quick rundown of them is:
-
Use the
:table
context if you need to query data with any of theMatcha.Table
functions, or to pass aMatcha.Spec.raw/1
spec to: -
Use the
:trace
context if you need to trace execution in running systems withMatcha.Trace
functions, or to pass aMatcha.Spec.raw/1
spec to::erlang.trace_pattern/3
:dbg
functions:recon_trace
functions
-
Use the (default)
:filter_map
or:match
contexts if you intend to play with specs and in-memory data using theMatcha.Spec
functions like:
Now that you're building Matcha
specs with the correct context for your use-case, you're ready to check out the usage guides and explore Matcha APIs!