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 to Matcha by changing Ex2ms.fun do... to Matcha.spec do...
  • Matcha.spec produces a Matcha.Spec struct, with an Erlang-API-ready spec from Matcha.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:

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 the Matcha.Spec call/2, run/2, and stream/2 functions
  • Use the :trace context if you intend to query data with Matcha.Trace functions
  • Use the :table context if you intend to trace code execution with the Matcha.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!