README.md

# 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
```