Excentrique - Opinionated syntax extensions to Elixir via macros

HI!

I wanted to share a small library for (currently) two Elixir macro based extensions, that I created for my projects, that I would like to get feedback on. I plan on possibly adding more eccentric features. Selecting only some with feature-flags or by separation into packages might become a goal. Currently they are backwards compatible, to the best of my knowledge.

defstruct

One extension will use the more or less regular type syntax for declaring and defining structs in one step.

defmodule SomeStruct do
  use Excentrique
  defstruct do
    id :: non_neg_integer()
    name :: binary() | nil \\ nil
  end
end

It is obviously similar to typed_struct, but I prefer the terser syntax above. It probably also lacks some features that typed_struct provides. It works for me.

implicit with

The other extension allows the use of <- outside of with expressions. They can currently only be used in the top-level of a function body.

As much as every function has an implicit try-block wrapping the body, using the <- will wrap the body with a with-block and thereby reduces nesting in a similar manner as the implicit try.

defmodule SomeModule do
  use Excentrique
  def some_function(arg) do
    {:ok, value} <- external_function(arg)
    IO.puts("Make more stuff here")
    {:ok, value} <- more_function(value) \\ {:failed_more, value}
    value
  else
    {{:failed_more, value}, {:error, reason}} -> {:more_function_failed, reason, value}
    {:error, reason} -> {:error, reason}
  end
end

Traditionally the else-block can also be used in a function body in conjunction with raise and/or catch. This contradiction is prevented during compile time, so that no implicit try can be mixed with implicit with-blocks.

To avoid ambiguity ({:error, reason} is a common case) or to pass additional data into the else branch, you may use the \\-operator. It tells the underlying mechanics to wrap the failing match of the match-signment in a 2-tuple, the first value being the operand to the \\-operator. Let’s call it mismatch annotation. I’m not entirely happy with the else block matching, but I still like it better than manually wrapping the <--expressions with an annotation on both sides, like you would have to before: {:failed_more, {:ok, value}} ← {:failed_more, more_function(value)}. I can also imagine, that


else
  failed_match, mismatch_annotation -> ...

Would give a nicer feeling. I’m not decided yet.

Both features are opinionated and don’t make a day and night difference to before. They are from my personal projects, but maybe someone cares to comment on their merit or even want’s to use them. As said before, both are backwards compatible and wouldn’t break any code, if they where enabled through a precompiler or even added to the language itself. I believe.

There’s a tad bit more info in the repository. Available currently only on github.

8 Likes