What my defguard did was you could define something like:
defguard is_exception(%{__struct__: struct_name, __exception__: true}) when is_atom(struct_name)
This could then by used like:
def blorp(_exc) when is_exception(_exc), do: "exceptioned"
def blorp(val), do: "No-exception: #{inspect val}"
And it does expand as expected. Specifically since _exc
is passed into the is_exception
âguardâ then the _exc
in the argument list gets a matcher added to it, while it also added the guard in the is_exception(_exc)
position. You could even match âoutâ something by passing in more arguments in some custom defguard, but essentially the above example turned into:
def blorp(_exc = %{__struct__: struct_name$1, __exception__: true}) when is_atom(struct_name$1), do: "exceptioned"
def blorp(val), do: "No-exception: #{inspect val}"
It basically just allows treating a guard as something that can both match and guard at the same time. This is more powerful than just what expat does now because currently expat can only add a single matcher and N guards, where with my defguard pattern above it allows for N matchers and N guards, like in this convoluted example:
defguard can_vote?(%Human{birthdate: utc_bd}, now) when (utc_bd + 60*60*24*365*18) >= now
...
def vote(human, now) when can_vote?(human, now), do: ...
def vote(_, _), do: ...
Or whatever, the thing is that you can pass in multiple things, and notice that you can pass âoutâ as well:
defguard bloop({a, b}, b) when is_atom(a)
...
def blorp(c) when bloop(c, d), do: d
So here you can see that it passes âoutâ something as well, and you just put a binding there. Notice that you can put a specific value there too like:
def blorp(c) when bloop(c, :ok), do: ...
So now that will only match {a, :ok}
where a
is an atom. The thing is that instead of just being 1-matcher/N-guards, it is N-matchers/N-guards, and since it is in the guard position then you can use in_guard?/1
as usual to do the right action if in a guard or make it an expression, which means you could use it in statement positions properly like:
a = {:ok, :blah}
b = 42
if foo(a, b, c) do ... else ... end ...
And notice that you can match on yet âmoreâ things as well. I could easily imagine adding the capability of having it support static KWLists so you could do things like this too if you want:
def blorp(c) when bloop(c, out: d), do: d
# or
if foo(the_tuple: a, an_arg: b, out: c) do ... else ... end ...
Or whatever as well.
Yep, same with the defguard style too.
Instead of just supporting single matchers, what about supporting multiple?
Iâm not saying replace the existing functionality with this, but rather âaddâ the defguard stuff to expat as well, then it would have both the ability for N-Matchers/N-Guards via a âguardâ and for easy-inline 1-Matcher/N-Guards via the matcher style too.
Expansion is not limited to being used as part of when, it can be used as an expression with defguard as well as it essentially just appears as any normal function call.