View Source Adopting Matcha
Mix.install(
[
{:matcha, github: "christhekeele/matcha", tag: "stable"},
{:ex2ms, ">= 0.0.1"}
],
force: true
)
overview
Overview
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.
reading-raw-specs
Reading Raw Specs
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.
comparing-generated-specs
Comparing Generated Specs
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.source/1
:
Matcha.Spec.source(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—they 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.source(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.
SUMMARY
- You can move from
ex2ms
toMatcha
by changingEx2ms.fun do...
toMatcha.spec do...
Matcha.spec
produces aMatcha.Spec
struct, with an Erlang-API-ready spec fromMatcha.Spec.source/1
- Both compilers may produce different but semantically equivalent specs
Matcha
will issue familiar Elixir compiler warnings for the Elixir code you give it
wrapping-raw-specs
Wrapping Raw Specs
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: simply provide them to Matcha.Spec.from_source!/1
to get a Matcha.Spec
struct. For example:
raw_spec = [{{:"$1", :"$2"}, [{:>, :"$1", 10}], [:"$_"]}]
{:ok, matcha_spec} = Matcha.Spec.from_source!(raw_spec)
testing-specs-with-erlang-apis
Testing Specs With Erlang APIs
choosing-matcha-contexts
Choosing Matcha Contexts
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_source!/1
to wrap existing ones
We can access their raw match spec source with Matcha.Spec.source/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 (default)
:filter_map
or:match
contexts if you intend to play with specs and in-memory data, using theMatcha.Spec
call/2
,run/2
, andstream/2
functions - Use the
:trace
context if you intend to query data withMatcha.Trace
functions - Use the
:table
context if you intend to trace code execution with theMatcha.Table
functions
using-matcha-apis
Using Matcha APIs
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!