This is a really nice project. Since you are exploring things on the area, here are some challenges/questions you can consider:
-
Is it possible to extend expat to be a generalization of records? defpat contains an exact subset of the records functionality, which is the pattern matching one. It is also easy to see how defpat
could be used for updates, all you need to do is to match on the value and then build another new value of exactly the same shape with the same variables in place except the ones you are replacing, etc.
-
Have you considered using another operator instead of =
for mixing patterns? I believe your mixing of patterns is actually an intersection. If you assume that %{"id" => id}
will match all maps with the "id"
field, including %{"id" => "foo", "name" => "baz"}
, and the pattern %{"name" => name}
all maps with a "name"
field, including the map mentioned above, when you specify id() = name()
you are actually saying it should have BOTH patterns, id()
AND name()
. If you think of patterns as sets representing the structures they can match on, it is an intersection. Another reason to choose another operator is that the precedence will work in a way it won’t require parentheses, for example: defpat subject id() &&& name()
. Here is a list of operators.
There is one feature we could add to Elixir that would allow the library to become more powerful which is to allow guards inside patterns:
def is_foo_or_bar(atom when atom in [:foo, :bar])
Of course nobody would write such in practice but supporting it would allow you to express patterns with guards:
def is_foo_or_bar(foo_or_bar())
Which the Elixir compiler would then rewrite internally as:
def is_foo_or_bar(atom) when atom in [:foo, :bar]
Anyway, this is very exciting and it is close to topics I am currently researching. I could expand more on both points above if you are interested. For example, if you are able to generalize defpat to records, you should also be able to generalize it for map updates, and if you define a pattern such as:
defpat foo_bar_baz %{"foo" => {bar, baz}}
And then allow someone to update the nested tuple like this:
map = %{"foo" => {1, 2}, "hello" => "world"}
foo_bar_baz(map, bar: 3)
#=> %{"foo" => {3, 2}, "hello" => "world"}
Optimizations could allow you to compile foo_bar_baz
to %{map | "foo" => Map.fetch!(map, "foo") |> put_elem(0, 3)}
.