# Expat - Elixir reusable, composable patterns <a href="https://travis-ci.org/vic/expat"><img src="https://travis-ci.org/vic/expat.svg"></a>
## Extracting patterns into reusable bits
Pattern matching on function heads is *the alchemist way* for dispatching to the
correct function on Elixir. However if a patterns get very large (I've seen people who could split their code better having _large_ patterns on their phoenix controllers due to nested patterns like maps inside maps, or matching on many parameters) then code could turn a bit ugly (IMHO) forcing the reader's eyes to parse the whole pattern and then discover where the actual
function logic starts.
```elixir
# Like tinder, but for brains
# (actually a project from a friend who asked to codereview with her and thus expat was born)
defmodule Brainder do
# brain match two subjects if their iq difference is less than ten
# requires both of them to have email and location in their structure
def brain_match(subject_a = %{
"iq" => iq_a,
"email" => _,
"location" => %{
"lat" => _, "lng" => _
}
},
subject_b = %{
"iq" => iq_b
"email" => _,
"location" => %{
"lat" => _, "lng" => _
}
}) when abs(iq_a - ia_b) < 10
do
# finally, actual logic here
end
end
```
## Usage
`Expat` provides a `defpat` (define pattern) macro for moving away those patterns into resusable bits (expatriating them from the function head)
```elixir
defmodule Brainder do
# provides `defpat` and `defpatp` for defining public and private patterns.
include Expat
# defpath takes a name and a pattern it will expand to:
defpat iq(%{"iq" => iq})
defpat email %{"email" => email}
# patterns can be reused inside others
defpat latlng %{"lat" => lat, "lng" => lng}
defpat location %{"location" => latlng(...)}
# *mixing* patterns is done by just using the `=` match operator
# thus subject is something that has iq, email and a location.
defpat subject(iq = email = location)
# the function head is more terse now, while still having access to the inner
# iq on each subject, and ensuring both of them have the same email, location fields
def brain_match(subject_a = subject(iq: iq_a),
subject_b = subject(iq: iq_b))
when abs(iq_a - ia_b) < 10 do
# logic here, distance from function head to this line is shorter
# while still explicit on what variables we can use here
end
end
```
Notice how `subject(iq: iq_a)` tells expat we only need to extract the value of `iq` from
the subject, while still matching all of its structure, thus expanding to:
```elixir
%{
"iq" => iq_a,
"email" => _,
"location" => %{
"lat" => _, "lng" => _
}
}
```
Similarly, you can just validate the pattern structure without extracting values with `subject(_)` which expands to:
```elixir
%{
"iq" => _,
"email" => _,
"location" => %{
"lat" => _, "lng" => _
}
}
```
One nice thing about `expat` patterns is that because they are generated as macros, they can be used anywhere a
pattern can be used in Elixir `with`, `case`, as the left side of a `=` match, like in tests
```elixir
test "dude is smart", %{dude: dude} do
assert subject(iq: 200) = dude
end
test "subject() binds all variables inside it", %{dude: subject()} do
assert iq > 200
assert email == "terry.tao@example.com"
end
`````
For example, you could export the `Briander.subject` pattern in a library and have nice people to use it for matching on things with that pattern (maybe before passing them to your api).
```elixir
def ZombieCoder do
. # use Brainder to search for brains, not love
. require Brainder
.# find and eat juicy brains
.def braaaaains() do
. .World.population
. |> Stream.filter(fn Brainder.subject(iq: iq, lat: lat, lng: lng) where iq > 200 -> {lat, lng} end)
.|> Stream.map(&yuuuumi_eaaaat/1)
end
end
```
## Installation
[Available in Hex](https://hex.pm/packages/expat), the package can be installed
by adding `expat` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:expat, "~> 0.1"}]
end
```