Hacker News new | ask | show | jobs
by salamanderman 1924 days ago
I played with Nim a little bit, loving it for a while, and I quickly ran into the same issue I had with Julia, which was that I had trouble staying in the basic language. Both Nim and Julia's documentation, and many modules/imports/includes/whatever of those languages, immediately jump to "holy shit we have macros! Importing this module changes the syntax! Isn't that awesome?!" And I'm like, no, it's not awesome. New syntax, new syntactic sugar, etc. is a learning curve every time for me. I'm often skeptical of operator overloading in C++ and python, so macro crazy languages are even worse. I feel like I must be in the minority, or I haven't hit that programming nirvana.
4 comments

...except that macros don't change the syntax of the language!

They just offer convenience on top of it, most common example is the `=>` lambda operator from the `sugar` module. I do agree, that the pattern matching macro presented in the article is a bit hard to get used to, but you don't have to, if you don't like pattern matching. And of course there are plenty of alternatives available as well, the simplest one imo is https://github.com/andreaferretti/patty

In all non-trivial codebases you have to learn how other people implemented something. It can be easy or take time depending on the how well it's written.

Macros are not different than functions: one can create readable or crazy spaghetti code in any language.

If you find a codebase full of unreadable macros it's not different than any other type of bad code: stay away from it or simplify it.

Personally, I'm yet to find a macro that makes the code less readable or more difficult to understand.

Yes! I'm constantly seeing people enthuse about how awesome macros are, and done well they're great, but I don't want every library I use to have a whole new set of syntax.
While I don't claim to have a comprehensive numbers, I did a quick `rg` on a full clone of nimble package list, and found that macros constitute approximately 0.4% of all definitions (functions, procedures, templates, methods).

  proc:      401673
  macro:     1869
  template:  16255
  func:      8746
  converter: 3538
  iterator:  1833
  method:    9025
You would be surprised how much can be accomplished just using simpler language constructs - even for the complex operations. Manual for example advises to use the least powerful construct for the job https://nim-lang.org/docs/manual.html#macros-caseminusof-mac...

  > Style note: For code readability, it is the best idea to use the least powerful programming construct that still suffices. So the "check list" is:

  >    Use an ordinary proc/iterator, if possible.
  >    Else: Use a generic proc/iterator, if possible.
  >    Else: Use a template, if possible.
  >    Else: Use a macro.
Also - notable portion of the commonly used macros is for embedded DSLs that in other languages would require you to use a separate build step with some external tool, or give way to horrible clutches like operator overloading-based DSL.

Note that I did the full clone several months back, and it is missing around two hundred packages that were added in this time, but I doubt it changes numbers greatly.

While that may be true, my experience was that you immediately see folks assuming you're using the 'sugar' macros, and then I went to do something with JSON and that was macros as well. Honestly curious, while .4% of all definitions are macros, how many things are using those macros? That's probably hard to tell, but I feel like that would be closer to representing the need for learning all the macros. I hear you, though, that I might have been nearing the end of the steep part of the learning curve.
Sometimes endowing existing syntax with richer semantics requires almost no documentation because things "just work". For example, cligen [1] uses a macro to synthesize an argv parser -> call dispatch CLI system. It could almost have been a template but it needs to loop over parameters more than once (and then grew a lot of features).

Anyway, since Nim has user-defined new operators, using those might also bother some. E.g., there could be a proc `=>` meaning something very different from that sugar module macro of the same name. Really, these work just like other procs modularity-wise. The syntax/grammar does not actually change, though. It's just flexible in the first place.

These complaints sound more like excuses to me. Every language has calls, styles, idioms, etc. you need to learn as a client of whatever it is you're calling/doing. And every language has quirky syntax corners. E.g., many are unaware that C lets you say "(a,b,c,d)" { which evaluates but ignores the result of "a,b,c" }. Yet you still often hear people proclaim how simple C syntax is. "Compared to what?", I guess (e.g. C++).

I do get where you are coming from "overloading"-wise - not just operators, but also regular procs (which in Nim are the same...there is just a proc `<`). When learning an alien language/code-base/libraries it really helps to have jump-to-definition. Nim via nimsuggest has great support for this if you configure your editor (vim, emacs, VisualStudio Code, etc.). You may have skipped that step but might love it more if you did it next time. nimsuggest can be aware of local and global --path modifications and the type context of the jump-to-definition. So, it can do a much better job than, say, ctags. (and yes, nimsuggest works for all the various definition types like macros, templates, etc., even the ones named like an operator such as `<`).

[1] https://github.com/c-blake/cligen

I totally agree with you, that's why I started Nimlings to help new Nim engineers get familiar with the language. Let's face it, you probably won't need macros WELL into your Nim lifecycle.

https://github.com/sergiotapia/nimlings