TaggedWith (TaggedWith v0.1.0)

Copy Markdown View Source

A with that tags each clause's failure by name, so else can tell which step failed even when two clauses can fail with the same-looking value.

Plain with matches each clause's else value on whatever shape it happens to have. That falls apart the moment two clauses can produce the same-looking value — say, validate_email/1 and validate_age/1 both returning {:error, :invalid} on failure. In else, a pattern like {:error, :invalid} can't tell you which one actually failed; both clauses funnel into it identically.

Tagging each clause by name fixes that, and it's not complicated — just {name, pattern} <- {name, expr}. But it means writing the name twice per clause, by hand, every time:

with {:email, :ok} <- {:email, validate_email(params)},
     {:age, :ok} <- {:age, validate_age(params)} do
  :ok
else
  {:email, {:error, reason}} -> {:error, {:invalid_email, reason}}
  {:age, {:error, reason}} -> {:error, {:invalid_age, reason}}
end

tagged_with/2 lets you write the bare pattern you'd use in a plain with (:ok, {:ok, value}, ...) and the name once per clause; the macro expands it into exactly the form above, so else can tell the two {:error, :invalid} results apart even though neither clause's own return shape does:

import TaggedWith

tagged_with email: :ok <- validate_email(params),
            age: :ok <- validate_age(params) do
  :ok
else
  {:email, {:error, reason}} -> {:error, {:invalid_email, reason}}
  {:age, {:error, reason}} -> {:error, {:invalid_age, reason}}
end

else is optional, exactly like in plain with. Without it, a failed clause's tagged value (e.g. {:email, {:error, reason}}) is returned directly — no exception, no special no-match behavior, since that's already how with behaves without else.

Summary

Functions

tagged_with(clauses, list)

(macro)