View Source MatchSpec (match_spec v0.3.3)

Elixir module to help you write matchspecs.

contains functions which transform elixir-style functions into erlang matchspecs, and vice versa.

functions-to-matchspecs

Functions to matchspecs

For transforming elixir-style functions into matchspecs, the following restrictions apply:

function-form

Function form

  • The function must use the Kernel.fn/1 macro as its form, or use defmatchspec/2 or defmatchspecp/2, where the matchspecs form is similar to the Kernel.fn/1 form
  • The function must have arity 1.

function-argument-matching

Function argument matching

  • The function may only match whole variables or tuple patterns.
  • Only one tuple pattern may be matched.
  • if your tuple contains a binary pattern match the binary pattern may only consist of bytes and strings, and only the size/1 modifier is allowed.
    • bitstrings matching is not supported
    • conversions such as float are not supported.

guards-and-return-expression

Guards and return expression

  • The function may only use guards in its when section.
  • The function may only return a single expression that (optionally) uses guard functions to transform matches.

examples-allowed

Examples (allowed)

Binding a top-level variable

fn tuple -> tuple end

Top-level tuple match

fn {_, _, value} -> value end

When with guard

fn {_, _, value} when is_integer(value) -> value end

Limitations on local guards

local (defguardp) guards are only supported when the macro is inside of a function body, due to limitations on macro resolution timing.

The following use cases are currently NOT supported:

  • fun2ms/2 outside of a function body (inside the module body)
  • fun2msfun/4 outside of function body (:lambda, :def, or :defp)

The following use cases are currently supported:

Structure matching inside a tuple

fn {%{key: a}} -> a end

Binary matching inside a tuple

fn {<<"foo" :: binary, 42, a :: binary>>} -> a end

Result expression modification by guards

fn {a, b} -> a + b end

examples-disallowed

Examples (disallowed)

Arity not 1

fn foo, bar -> foo + bar end

Multiple tuple matches

fn {_, :foo} = {:bar, value} -> value end

Non-tuple top level match

fn "foo" <> bar -> bar end
fn %{foo: bar} -> bar end

Disallowed type in binary match

fn {<<foo :: integer-big-endian>>} -> foo end

Non-guard function in match

fn {a} when String.starts_with?(a, "foo") -> a end

Non-guard function in result

fn {a, b} -> a ++ b end

Note

The restrictions on binary matching exist due to limitations on the BIFs available to ets and may change in the future if OTP comes to support these conversions in its kernel.

Link to this section Summary

Functions

Writes a matchspec-generating function based on a body.

Writes a matchspec-generating function based on a body.

converts a function ast into an ets matchspec.

converts a function into a function that generates a match spec based on bindings.

converts a matchspec into elixir AST for functions. Unfortunately, the ast generator cannot guess names for variables, so variable names are set by the numerical value of the matchspec token

Link to this section Functions

Link to this macro

defmatchspec(header, list)

View Source (macro)

Writes a matchspec-generating function based on a body.

You may provide multiple function bodies.

This macro uses the same backend as fun2msfun/4 and will generate identical code to that macro.

Example:

use MatchSpec

defmatchspec my_matchspec(value) do
  {key, ^value} when key !== :foo -> key
end

This generates the equivalent to the following function:

def my_matchspec(value) do
  [{{:"$1", :"$2"}, [{:"=:=", :"$2", {:const, value}}, {:"=/=", :"$1", {:const, :foo}}], [:"$1"]]
end
Link to this macro

defmatchspecp(header, list)

View Source (macro)

Writes a matchspec-generating function based on a body.

You may provide multiple function bodies.

This macro uses the same backend as fun2msfun/4 and will generate identical code.

see defmatchspec/2 for details

Link to this macro

fun2ms(fun, opts \\ [])

View Source (macro)

converts a function ast into an ets matchspec.

The function must also be "fn ast"; you can't pass a shorthand lambda or a lambda to an existing lambda.

The function lambda form is only used as a scaffolding to represent ets matching and filtering operations, by default it will not be instantiated into bytecode of the resulting module.

iex> require MatchSpec
iex> MatchSpec.fun2ms(fn tuple = {k, v} when v > 1 and v < 10 -> tuple end)
[{{:"$1", :"$2"}, [{:andalso, {:>, :"$2", 1}, {:<, :"$2", 10}}], [:"$_"]}]

You can use variables from the calling scope in the filters.

iex> require MatchSpec
iex> my_atom = :foo
iex> MatchSpec.fun2ms(fn tuple = {k, _} when k === my_atom -> tuple end)
[{{:"$1", :_}, [{:"=:=", :"$1", {:const, :foo}}], [:"$_"]}]

If you would also like the equivalent lambda, pass with_fun: true as an option and the fun2ms/2 macro will emit a tuple of the matchspec and the lambda.

iex> {ms, fun} = MatchSpec.fun2ms(fn {:key, value} -> value end, with_fun: true)
iex> :ets.test_ms({:key, "value"}, ms)
{:ok, "value"}
iex> fun.({:key, "value"})
"value"

This macro uses the same backend as fun2msfun/4 and will emit the same matchspec as if you passed no parameters to fun2msfun/4

Link to this macro

fun2msfun(type \\ :lambda, name \\ nil, fun_ast, bindings)

View Source (macro)

converts a function into a function that generates a match spec based on bindings.

This can be used to either create a named function or an anonymous function. If you would like to use one of the free variables in your function as a part of the head of the match, you must pin it.

if you omit the first parameter, it will create an anonymous function.

basic-example-with-lambda-default

Basic example with :lambda (default):

iex> require MatchSpec

# using a variable in the match
iex> lambda = MatchSpec.fun2msfun(:lambda, fn {key, value} when key === target -> value end, [target])
iex> lambda.(:key)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}], [:"$2"]}]

#pinning a variable
iex> lambda2 = MatchSpec.fun2msfun(fn {^key, value} -> value end, [key])
iex> lambda2.(:key)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}], [:"$2"]}]

Note that the bindings parameter acts like pattern matching on function arguments: They may use complex matches and there can be more than one, the arity of the anonymous (or def/defp) function matches the length of the bindings argument.

iex> require MatchSpec
iex> lambda = MatchSpec.fun2msfun(:lambda, fn {^key, ^value} -> true end, [%{key: key}, value])
iex> lambda.(%{key: :key}, :value)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}, {:"=:=", :"$2", {:const, :value}}], [true]}]

example-with-def-defp

Example with (:def/:defp):

require MatchSpec

MatchSpec.fun2msfun(:def, :matchspec, fn {key, value} when key == target -> value end, [target])

converts a matchspec into elixir AST for functions. Unfortunately, the ast generator cannot guess names for variables, so variable names are set by the numerical value of the matchspec token

The second parameter takes two modes:

  • :ast emits elixir ast to write a lambda.
iex> MatchSpec.ms2fun([{{:"$1", :"$2"}, [], [:"$2"]}], :ast)

{:fn, [],
  [{:->, [], [[{:{}, [], [{:v1, [], nil}, {:v2, [], nil}]}], {:v2, [], nil}]}]}
  • :code outputs formatted elixir code.
iex> MatchSpec.ms2fun([{{:"$1", :"$2"}, [], [:"$2"]}, {{:"$1"}, [], [:"$_"]}], :code)

"""
fn
  {v1, v2} -> v2
  tuple = {v1} -> tuple
end
"""