Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

Not a Lisp user, but a heavy Nim macro user. Nim macros are AST based, which means that after the parser part of the compiler have read the code and created a tree structure of it your macro is called on this tree. Then you can modify this tree to your hearts desire and return a new tree that the Nim compiler will splice back into the main tree before continuing. This means that you rewrite pretty much anything into pretty much anything else. You also have access to reading files on the machine, or doing pretty much anything else that Nim offers. For examples of what you can do with it you can have a look at my macro to create GUIs: https://peterme.net/cross-platform-guis-and-nim-macros.html, my article on how I use macros to create more read- and maintainable code: https://peterme.net/metaprogramming-and-read-and-maintainabi... or perhaps see how Protobuf was written as a string parsing macro which reads a Protobuf specification and spits out all the types and procedures you need to use it: https://github.com/PMunch/protobuf-nim


sort by: page size:

Two things in common use:

Lisp has reader macros that can alter lexical analysis and parsing; correct me if I am wrong, but I think that’s not possible in Nim. E.g. things like JSX are trivial to implement in Lisp.

Also, lisp macros let you e.g. write new control structures with multiple “body” parts - iirc, in nim only the last untyped macro arg can be a code body (you can put a block in parantheses, but that’s not as elegant)

I’m sure there’s other stuff that fexprs and other [a-z]exprs can do that nim can’t, but i’Ve never seen them in use (or used them myself)

Also, personally I think Nim’s model is more practical; lisp’s power essentially requires lispy or REBOLy syntax to be usable. Nim is pascalythonesque, and though complex is not complicated; much like Python, and unlike C++, you can make use of the ecosystem without being afraid of obscure details biting you - but it has all the capabilities when you need them.


Still, isn't it AST-based in the sense that the input of a Lisp macro is effectively a parsed syntax tree, just as in Nim?

Fair enough I guess. If the Lisp macro system only sees parsed s-exps, in principle you still need to figure out for yourself what kind of construct you are dealing with on a higher level of abstraction. Nim's macro system seems to be operating on a higher level of abstraction in that regard.

First, can you talk a little about how Nim's macro system is more-powerful than Lisp's? That seems like a fairly broad claim, and unless it allows you to influence the compiler at compile time (and maybe even then), I am curious how that works out.

Second, one of the biggest issues with lisp is that macros end up sort of slow, especially when you're using them + lists to replicate ADTs with pattern matching (versus, say, ML, which can do very fast dispatch based on types). Doesn't Nim fall into that same trap?


It's lispy in what it can do, not in having a rat's nest of parens, true.

I'd love a concrete example of a macro you could express in lisp and not in nim.

But it's macros absolutely operate on a syntax tree - refer to https://nim-lang.org/docs/tut3.html#introduction-the-syntax-...


You're really very wrong on Nim's macros. It follows Lisp's defmacro tradition instead of Scheme syntax-rules/syntax-case, but that doesn't make it any less powerful (many would argue it's demonstrably more powerful). You are also dead wrong on syntax-rules/syntax-case capabilities, or maybe on what the syntax/AST is, if you think that there's anything they can do that Nim can't. Both systems deal with AST which means they both are unable to introduce new ways of parsing the code, only transform already parsed one. In (some) Scheme and Common Lisp you get access to readtable, which is the parser, but that's really a different thing. And even in Lisps it's not that popular: Clojure and Emacs Lisp disallow this for example.

Personally I favour pattern-based macros, like the ones implemented in Dylan, Elixir or Sweet.js (to show some non-sexp-based languages with such macros); but there is nothing "wrong" with procedural macros and they are not, in any way, less robust.

You don't have to be excited by Nim, but you should try to avoid spreading lies just because you aren't. Maybe a "lie" is too strong a word, but this statement: "Nim's macro system seems to be far less robust than that" is really very wrong and I wanted to stress this fact.


Does Nim really have read time macros? (https://letoverlambda.com/index.cl/guest/chap4.html)

I don't understand what you mean by more flexible input syntax? Lisp hardly has any syntax to begin with; it's one of my favorite things about the language. (If that's a problem for a particular usecase, the aforementioned read time macros can change that.)

Regarding giving the macro body access to the types, that's because Nim is statically typed. I'm sure Lisp would do the same if it were as well, but it's (mostly) dynamic and that's life.


Yes, but Nim's macro system, while very powerful, is, at present, the most clumsy and awkward thing I've seen. it's imperative, like Lisp macros. However, unlike Lisp macros, there are no templates, and the datastructure that Nim's macros manipulate is much closer to the actual AST, and thus far more complex. Because of this, Nim macros must clumsily plug together an AST, and do so in a manner that's so noisy that you have to squint to see what it's actually doing by the time you're done. Meanwhile, Lisp macros are not necessarily shorter, but it's far easier to see what's going on.

Lisp macros operate on a representation of the AST.

By overhead I mean the nim compiler can generate direct specific instatiations at compile time instead of doing runtime based duck-typing as in lisp.

Like, you can have a macro that generates a different expansion based on the type of arguments passed, if you structure the macro that way.

The macro can also take entire code blocks as arguments of course, so you can implement general purpose control structures that are first class citizens.

Here's an example: https://peterme.net/cross-platform-guis-and-nim-macros.html


Lisp macros?

Thank you so much for this thoughtful reply — I enjoyed the code samples and the details on Lisp's macro system, and I can see how its approach gives you more control to invent on top of the language. It's given me more to read and think about.

If you were targeting a Lisp you could do this as a (macro) library

Easily. The macro gets handed the ast of the input. It can interpret it however it wants. There are no reserved words or anything like that. Much of nim is implemented as macros, just like most lisps

Disclaimer: I only did a hobby project in Lisp once. But I did use some of the macro functionality.

Judging by some of the comments here, it seems like the macro system has a similar approach as Lisp's macro system, which is also AST-based. Something I don't see here is macros that generate other macros, but the question is how much you really want that anyway (when I did that, I thought the syntax was horribly complicated because of all the quoting). I know Lisp also has reader macros (they run before the parser) that allow you to effectively change the language syntax, but I didn't use those.


It's true that macros exist in languages like Nim and Elixir, even C. They either use something more akin to string templates (expanding a macro fills in the holes) or a DSL for manipulating the AST (something new to learn to write code that writes code).

One reason Lispers find macros so beautiful is the simplicity of writing macros using the exact same list processing functions. The code-is-data thing is reinforced through the fact that the AST is one-to-one with the syntax.


> There's nothing like them in Nim, Java, or C++.

Are you sure?

Nim supports direct access to the AST so macros can be used to define new programming constructs like in Lisp.

Also other languages support things like that (as library), Java for instance:

http://www.antlr.org/

http://tunnelvisionlabs.com/products/demo/antlrworks


Of course they're powerful. They're true imperative macros, the very thing that all Smug Lisp Weenies worship (I don't, because I'm not an SLW, but they are Pretty Cool). The reason I think the macros are clumsy is pretty simple: Nim is really, really bad at manipulating its own AST: It shuffles around, munging data, and by the end, you have no idea what the result should look like. Unlike Lisp, there are no templates in macros, which make seeing what the result will be very easy.

This is how a debug macro, a very simple one, is implemented in Nim (taken from the tutorial):

  macro debug(n: varargs[expr]): stmt =
    result = newNimNode(nnkStmtList, n)
    for i in 0..n.len-1:
      result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i])))
      result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": ")))
      result.add(newCall("writeLine", newIdentNode("stdout"), n[i]))

What the heck is going on? This is the equivalent Lisp (specifically, Chicken Scheme, the dialect I am most familiar with, but it should be similar in most lisps with imperative macros):

  (define-syntax debug
    (ir-macro-transformer
      (lambda (e i c)
        (let ((stmts (cdr e)))
          (map (lambda (stmt)
                 `(begin (write ,stmt)
                         (display ": ")
                         (newline)) 
               stmts)))))
That's much easier to understand. It might be a bit less concise, but it more than makes up for it.

I don't blame Nim for being bad at manipulating its own AST. Lisp is pretty bad at it, too (no, lists are not the lisp AST, not in any lisp suitable for the Real World. I don't know who told you that, but it's a lie: Most lisp ASTs are just as complex as Nim's). What I do blame Nim for is not being potentially homoiconic so there's no easy datastructure to represent code in, or at least providing a datastructure that's easier to manipulate, or at the very least provide some mechanisms to make the existing structures easier to manipulate.


> However, unlike Lisp macros, there are no templates

That's wrong. Nim has both macros and templates.

> Lisp macros are not necessarily shorter, but it's far easier to see what's going on.

Even Lisp macros are not perfect. You always have to care for new symbol names where the lexical scope could be affected.


Some Lisp advocates may tell you that Lisp even does not have hygienic macros. Lisp dialects like Scheme have. Lisp usually has procedural macros, which transform source code (in the form of nested lists), not ASTs (like Nim).

That Nim has 'powerful hygienic macros' is fine, many languages have powerful macros.

next

Legal | privacy