Qqwy

Qqwy

TypeCheck Core Team

This morning, I came to a sudden realization: Elixir’s Macro system is even more powerful than I’d thought.
My mind is blown.

Let me try to explain:

Macros take one or multiple expressions as input. These expressions are not evaluated before passing them into the macro. Instead, they are passed in as AST (an Abstract Syntax Tree). If, when and how we want to evaluate these ASTs is completely up to the macro implementation.

Observe the following example:

defmodule Symmath do
  defmodule Expr do
    defstruct [:ast]
  
    defimpl Inspect do
      def inspect(expr, opts) do
        ast_str = Macro.to_string(expr.ast)
        "Symmath.expr(#{ast_str})"
      end
    end
  end


  @doc """
  Creates a Symmath Expression.
  `expression` is not immediately evaluated.
  Instead, it is treated as a symbolic representation.
  """
  defmacro expr(expression) do
    escaped_expr = Macro.escape(expression)
    quote do 
        %Expr{ast: unquote(escaped_expr) }
    end 
  end
end

Paste this in IEx or something, and call it as follows:

require Symmath
Symmath.expr(x*2 + 3*pi = pow(x, 2))

These things that blew my mind:

  • neither x or pi have to be defined.
  • functions like pow do not actually need to be defined. They are just ‘abstract syntax’ as everyting else and we can decide what to do with it at a later time.
  • we can just use any valid Elixir operators in this expression, and change their meaning or manipulate them if we want.
  • Things like Symmath.expr(100/0) are completely valid statements.

The example above only reads the AST, puts it into a struct, and when someone inspects this struct, it shows the stringified version of the AST inside. But we could now pass these structs around, write ways to simplify these expressions, rewrite them, evaluate them (whole or partially), etc.

This opens up a whole new world for Domain Specific Languages.

I think we can build some really crazy and amazing things with this idea!

Showing Posts 1 to 10

martin

martin

Yeah, it sort of becomes a game of “is this valid Elixir syntax?”. Just quote expressions and have a look. For example, pow(x, 2) is fine, but in an equation dsl, it might be nice to write x^2. Valid syntax.

Qqwy

Qqwy OP

TypeCheck Core Team

Yep, things that are special characters(of whom Elixir has very few) or attempting to use unary operators on two operands are the two things that are not possible. That basically means that # and ^ are out. Nearly all other things are fair game.

StevenXL

StevenXL

Macros are one of those items that are conceptually simple but hard to read and utilize in practice.

Each piece is simple in isolation. Macros are special functions that receive an AST and return an AST. quote/2 turns an expression into an AST. unquote/1 evaluates an expression and injects the results into a quoted expression (a.k.a. an AST fragment). And of course you have the concept of “hygiene” - variables created in a macro are private to that macro unless explicitly defined otherwise.

So again, the pieces are, on their own, easy to wrap your head around, but putting them together I’ve found difficult.

There are two great resources out there for learning Elixir’s metaprogramming capabilities - @chrismccord’s Metaprogramming Elixir and @sasajuric’s Understanding Elixir Macros mini-series. Head still hurts, and it will probably take two readings of each to have macros really sink in for me. (Oh, and of course the documentation on quote/2 is incredible).

I’m not sure what the point of my post is/was now. :101:

martin

martin

So ^ is a bit weird.

quote do 1^2 end
** (SyntaxError) iex:1: syntax error before: '^'

quote do x^2 end
{:x, [], [{:^, [], [2]}]}

It would be ill-advised to include an operator with the gotcha that it cannot operate on a literal.

Qqwy

Qqwy OP

TypeCheck Core Team

The problem here isn’t that ^ cannot operate on a literal (indeed, statements like ^2 are allowed), but that ^ is only defined as a unary operator (only taking a single input). For + and - this isn’t the case, as they have both a unary ( -2) and a binary variant ( 1 - 2).

rvirding

rvirding

Creator of Erlang

Of course the obvious solution to the problem of operators is to use lisp which doesn’t have operators, and practically no reserved characters. :laughing:

Qqwy

Qqwy OP

TypeCheck Core Team

Limiting the amount of definable operators was a deliberate choice by José Valim. Operators are a nice and concise way to express things, but nobody knows what <<^ or <*> or =>= or $~! means when you first encounter them. In that way, they are very implicit. This is the reason that languages like Haskell are deemed hard to understand, because they are very operator-heavy.

I understand the choice @josevalim made here, but I do frequently miss this freedom in Elixir.

OvermindDL1

OvermindDL1

iex(server@snip.local)3> quote do lit 1,^ 2 end
{:lit, [], [1, {:^, [], [2]}]}

Eh, its ugly, but for a dsl it ‘might’ work depending on the purpose (probably not a math one)…

OvermindDL1

OvermindDL1

On a side-note, it would be freaking awesome to get some kind of defreadmacro or so, it gives you the binary/string input stream until some specified ending ‘thing’…

  # THE HORROR
  defreadmacro python(stream, env) do
    # blah
  end

  # elsewhere
  python~(
  a = 2+3
  print "blah"
  )~

Hmm, although I guess you could already do things like that…

defmacro myDSEL(input) when is_binary(input) do
  LISPToElixir.parse_to_ast(input)
end

def do_something() do
  myDSEL """
    (Enum.map (1 3.14 42) (:fn (x) (IO.inspect x)))
    """
end

/me wonders if someone is actually going to make that now…

gregvaughn

gregvaughn

Ah, lisp, in which every system is its own dialect of lisp :wink:

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 92995 915
New
AstonJ
The obligatory hello world thread! Who are you and where are you from? :stuck_out_tongue:
4616 55835 594
New
caslu
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
matt-savvy
Is there a word for the ~> symbol used in Version strings? Do you also just call it a Squiggle Arrow™ ?!
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
aseigo
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews