Qqwy

Qqwy

TypeCheck Core Team

Today I realized how powerful macros really are

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!

Most Liked

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:

rvirding

rvirding

Creator of Erlang

That just shows how versatile lisp is, you are not bound to just one.

Seriously though, any language with macros gives you this feature/plague, even elixir. You just have to show restraint.

Qqwy

Qqwy

TypeCheck Core Team

@OvermindDL1 You could define a Sigil for something like that. This has the advantage that you don’t need to use "" around whatever you pass in.


I took the idea of creating a Symbolic Math library that used Elixir’s AST and started tinkering a little. The result is symmath.

It is still far from finished, and probably contains some bugs (I haven’t gotten around to writing tests yet, as the library is still in full flux), but it really is a lot of fun to work on.

Some code examples:

iex> import Symmath
iex> f = expr(1+1)
Symmath.expr(1+1)
iex> simplify(f)
Symmath.expr(2)
iex> g = expr pow(2*x-3, 3*pi)
Symmath.expr(pow(2 * x - 3, 3 * pi))
iex> g |> deriv
Symmath.expr(pow(2 * (3 * pi) - 3, 3 * pi) * (0 * pi + 3 * 1))
iex> g |> deriv |> simplify
Symmath.expr(3 * pow(6 * pi - 3, 3 * pi))

Both the simplification and the derivation can still be improved (Things involving multiple applications of the chain rule will go in an infinite loop right now, for instance). Also, there isn’t support for trigonometric functions or logarithms yet.

Still, the basic idea is there, so I’m interested about what you think :sweat_smile: .

Last Post!

lkuty

lkuty

You might find https://www.ast.ninja/ very interesting to see the AST of expressions. And its accompanying YouTube video at

Where Next?

Popular in Discussions Top

AstonJ
If a newbie asked you about Phoenix Contexts, how would you explain the basics to them? Feel free to be as concise or in-depth as you li...
New
sergio
There’s a new TIOBE index report that came out that shows Elixir is still not in the top 50 used languages. It also goes on to call Elix...
New
rower687
Hi all, I’ve been reading a lot about the “let it crash” term and how supervising processes and the whole messaging passing make an elixi...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement