Hacker News new | ask | show | jobs
by baggers 3668 days ago
I found them useful when I looked at clojure and saw their shorthand syntax for lambdas where #(* % %) is a lambda that takes one argument and squares it. You can get some tidy things that are similar with regular macros but to make it actually equivalent your need to hook into the reader..and that's what reader macros are for. So within the hour I had something that let me write λ(* _ _)

The best bit though, is that this isn't some nasty hack. This is supported by the spec, so I can package this up and let other people us it just like any other functionality we care to ship around.

That's my favorite thing really, being able to treat approaches to writing code in the same way we treat the functionality we make using code.

1 comments

> That's my favorite thing really, being able to treat approaches to writing code in the same way we treat the functionality we make using code.

You mean ... programming the programming language?

> I can package this up ...

Not really; it will clash with someone else's use of λ. Read macros do not "package" particularly well. (Racketlang has a good solution for this: you put a #lang whatever directive at the top of a file and then the rest of the file uses the special syntax associated with that lang name).

It's very easy to overestimate the usefulness of read syntax.

This feature of Common Lisp is used far less than newcomers might imagine, though there are some famous examples of it. If you randomly sample Lisp code, you are much more likely to find a macro than a read macro.

> So within the hour I had something that let me write λ( _ _)*

Do you plan to type "lambda" with args enough times to eventually that hour of your life back? Ha.

Useful syntax for writing anonymous functions can be had, without relying on read macros at all.

cl-op: https://code.google.com/archive/p/cl-op/

Note how if we take λ(* _ _) and then just move the parenthesis over the Greek symbol, we get (λ * _ _). This saves almost the same amount of typing. If your Lisp recognizes λ as a symbol token, then all you have to do now is write a regular macro. Basically, just get the cl-op package and then set up λ as an alias for op: (defmacro λ (&rest args) `(op ,@args)).

In TXR Lisp, I implemented a more powerful op which has numbered arguments, and an escape syntax for inner ops to refer to outer ops.

Y combinator with TXR Lisp op:

  ;; The Y combinator:
  (defun y (f)
    [(op @1 @1)
     (op f (op [@@1 @@1]))])

  ;; The Y-combinator-based factorial:
  (defun fac (f)
    (do if (zerop @1)
           1
           (* @1 [f (- @1 1)])))

  ;; Test:
  (format t "~s\n" [[y fac] 4])
Combinators return us to your topic of writing a function which squares its argument. Instead of a condensed lambda notation like (op * _ _) for doing this with syntax, we can have a combinator, like (dup #'). This is nicer in a Lisp-1 language where we can just do (dup ). Dup takes its argument, which is a two-argument function, and returns a one-argument function which calls that function with two copies of the argument:

  (defun dup (fun)
    (lambda (arg) (funcall fun arg arg)))

  (funcall (dup #'*) 2) -> 4

  (mapcar (dup #'*) '(1 2 3)) -> (1 4 9)
Hehe, what are curiously peeved sounding post. I was seeking to keep it short but as I've been unclear let's drill into some stuff.

> Not really; it will clash

Nope, named-readtables fixed the clashing reader-macro issue a good while back.

> You mean ... programming the programming language?

Yes, I do. I said it the way I did out of choice, as the 'programmable programming language' phrase has been bandied around so often that, for some, it has lost impact. Maybe that phrase doesn't inspire the same ahah moment in some as it does for other. Saying the same thing in a different way can be enough for someone to pick up something new. (It feels weird writing this down as it's kind of self evident)

> Do you plan to type "lambda" with args enough times to eventually that hour of your life back

I that hour was spent learning about reader macros, learning something new is of value to me, so there is no 'lost hour' to reclaim. Don't you just code for fun some days, without worrying if it is somehow ultimately useful?

> Note how if we take λ(* _ _) and then just move the parenthesis over the Greek symbol, we get (λ * _ _). This saves almost the same amount of typing

Ah ok, maybe this is where we parted mental ways. I'm not interesting in saving typing, that wasn't the goal. The point was to remove a little visual obstruction from what I was doing. For example (and yes it is a trivial example) (lambda (x) (* x x)) is 20 characters long, of which 8 are of interest (* x x) I liked that the shorthand reduced a little visual noise.

Yes there are many ways to skin this particular cat, but I liked the shorthand. I also like not having the lambda in the first position as it maintained a property of lisp code I generally enjoy, that I can run my eyes over the lefthand side of the forms and quickly get a rough feel of what is going on. Again, this is just a personal feeling, but some days I write code just for me, for fun, so why not make things pleasant for myself.

It's rather odd to get into a discussion about this, when the whole point is that we don't have to pick one golden way and enshrine it in the language. It's just a library, use it or don't, it doesn't matter.

Peace

named-readtables has largely solved the packaging issue of read macros.
Yep, named-readtables do a pretty good job on packaging read-macros. You've probably missed it.
Packaging means "better than just loading something and having it pee in the global readtable". It doesn't solve the problem that read macros compete for the same characters and clash.

Documentation for merge-readtables-into:

"If a macro character appears in more than one of the readtables, i.e. if a conflict is discovered during the merge, an error of type READER-MACRO-CONFLICT is signaled."

Exactly, and this is awesome. If there is a conflict, I get an error and can decide what to do.

At their core, reader-macros are abbreviations. Obviously there are a finite number of abbreviations one can use, so any time you want to add concise syntax from more than one source, there is a chance of conflict.

I can see how this would have value if your idea of Lisp programming is to bring half a dozen new read syntaxes, and use them all in the same source file. Plus keep up with upstream enhancements in all of them, so you have to guard against new clashes popping up.