Live data from Hacker News

When you get to be smart writing a macro

tonsky.me

1–10 of 28 posts

Re: When you get to be smart writing a macro

#4

  #p x
is one character less than

  (p x)
When code golfing Lisps you can remove all whitespace after a closing paren, but not after a symbol. So in the following fully golfed token sequences, #p loses its one character advantage:

  #p x y
  (p x)y
I bring up code golfing because that's what this is about, presumably.

But what if the argument is a parenthesized expression:

  #p(x)
  (p(x))
#p is back in the game with a 1 char lead.

The thing is, we can make the printing operator take arguments and turn them into an expression. Suppose we make a cousin of p called q, such that:

  (q x) -> (p (x))

  (q x y z) -> (p (x y z))
q no longer loses to #p:

  (q x)
  #p(x)

Re: When you get to be smart writing a macro

#5

#p x is one character less than (p x) When code golfing Lisps you can remove all whitespace after a closing paren, but not after a symbol. So in the following fully golfed token sequences, #p loses its one character advantage: #p x y (p x)y I bring up code golfing because that's what this is about, presumably. But what if the argument is a parenthesized expression: #p(x) (p(x)) #p is back in the game with a 1 char le…

I think what's more important than the character count is the fact that you can add #p with two key strokes.

Inserting parentheses requires moving your cursor around or invoking some shortcut in your editor if you use paredit, vim-surround, or a similar plugin. Applies equally for removing the invocation (although paredit makes that part easy).

Re: When you get to be smart writing a macro

#6
I don't think this is a good enough reason to write a macro. Macros should be used for doing things one otherwise couldn't do and have sufficient advantage. For example the threading macro used right there in the code is worth writing a macro, because it makes things easier to read and follow and couldn't be done like that using functions, because of the order of evaluation and argument injection that the threading does. Another example are new define forms, which by definition cannot be expressed resulting in the same syntax using functions. Defines are special. Another example for a justifiable macro could be a timing macro, which relies on changing the order of execution, because when you write (time something) then something would be evaluated before time is ever called, if time was a function.

But the #p in the post seems to me to be a dubious choice for writing a macro. Too specific, too easy to use something else, too much confusion, too little gain.

Re: When you get to be smart writing a macro

#8
I started using Clojure in 2009 and used it heavily for a long time. I even ran my local Clojure meetup for many years. Lately, I've moved away from Clojure however and today I mostly write Typescript, some hobby C++, some Rust. Gleam when I can (which is rare). Anyway, that's just context to say that I've written and enjoyed a lot of Clojure which made me think about macros a lot over the years.

I've come to the conclusion that macros are NEVER the right choice for normal application developers and only RARELY the right choice for library authors.

I would pick function over macros every single time.

Even in libraries, I feel that most uses of macros are unjustified. Even in cases where macros enable something that wouldn't otherwise be possible, I question whether its really the best way. For example, its pretty cool that core.async could be implemented as macros, but I feel that core.async has rather poor developer ergonomics because of it and building it into the language itself would have lead to a much better system with a much better developer experience. I have a number of reasons, but I'll just mention the biggest here: macros cannot see into functions, so core.async requires its async functions to be called directly within a go block (eg you cannot wrap core.async/Sometimes using macros means that things can be optimised at compile time (eg expanding core.match or specter selectors), but I feel these cases are pretty rare.

I do like many of the macros in clojure.core (threading macro etc) but I see these as a language implementation detail -- they could have been built into the language grammar or compiler and the end user experience would be the exact same.

I do wish Gleam supported some limited form of macros to generate code based on annotating types (kind of like Rust's derive), but I very much agree with Gleam's logic for not including macros, and my experience with Clojure has basically solidified my feelings that macros are very rarely a good idea.

Re: When you get to be smart writing a macro

#9

#p x is one character less than (p x) When code golfing Lisps you can remove all whitespace after a closing paren, but not after a symbol. So in the following fully golfed token sequences, #p loses its one character advantage: #p x y (p x)y I bring up code golfing because that's what this is about, presumably. But what if the argument is a parenthesized expression: #p(x) (p(x)) #p is back in the game with a 1 char le…

I think what's more important than the character count is the fact that you can add #p with two key strokes. Inserting parentheses requires moving your cursor around or invoking some shortcut in your editor if you use paredit, vim-surround, or a similar plugin. Applies equally for removing the invocation (although paredit makes that part easy).

Isn't this the exact same number of keystrokes? 'Shift-3 p' versus 'Shift-9 p' on my keyboard.
Post reply on HN