Seamlessly extending the Julia syntax



Is it possible to extend the Julia syntax seamlessly as in the Racket language:

“Racket eliminates the hard boundary between library and language, overcoming a seemingly intractable conflict. In practice, this means new linguistic constructs are as seamlessly imported as functions and classes from libraries and packages. For example, Racket’s class system and for loops are imports from plain libraries, yet most programmers use these constructs without ever noticing their nature as user-defined concepts.”
[Commun. ACM 61.3 (2018) pp. 62–71]

I mean eg., to add a new loop construct (without a need for macro symbol @):

loop until <cond>
   #... do something ...
end loop

Such a possibility is highly valuable for developing DSLs.

"Domain-Specific Languages" in Julia

In Julia, macros require a @. This may be unusual for programmers familiar with Lisp, but in the Julia community it is considered a feature, as it clearly indicates a potential source transformation.

Macros can perform arbitrary transformations on the AST, but since Julia does not use S-expressions, delimiting blocks have to rely on existing constructs. Eg

@myloop ... begin # note the begin

That said, an important practical lesson from Julia is that DSLs don’t have to be implemented with source transformations alone when you have a fast implementation of parametric types with multiple dispatch.


Could you please expand a bit on that?


An example is best, see eg:


Another example hiding in plain sight is JuMP.jl. While most of the calls to JuMP come in the form of macros that transform certain expressions such as list comprehensions, most of the linear algebra does not get transformed by the macros, but uses operator methods defined for AbstractArray{JuMP.Variable} objects. In fact, most of the time your expressions will only be in terms of these operators, so for a long time I was rather confused about what the macros were actually doing.

Julia syntax is far more complicated than that of most flavors of lisp, which makes metaprogramming far more difficult in Julia than in those languages. This was a sacrifice made for the sake of having a more user friendly syntax, and especially so that code can more closely resemble standard mathematical notations than it can in lisp. I always recommend MacroTools.jl as anything but the very simplest metaprogramming in Julia is frankly painful without it.


…yet most programmers use these constructs without ever noticing their nature as user-defined concepts.

Explicit is better than implicit in this case IMHO, thank you Julia devs for designing the language so that we are able to identify macro calls with @.

This is how your loop would look like:

julia> macro until(condition, block)
               while !$condition
           end |> esc
@until (macro with 1 method)

julia> x = 0

julia> @until x == 5 begin
           @show x
           x += 1
x = 0
x = 1
x = 2
x = 3
x = 4


That said, you can also implement your own parser and even do things like this:

Nowadays making your own REPL mode is easier than ever: