Hacker News new | ask | show | jobs
by Jach 472 days ago
A late reply but it's worth addressing one way of doing this. First, your concern about object not being a valid function or macro isn't relevant at read time. Second, note that Lisp already has similar syntax: '(1 . 2) is essentially (cons 1 2). Implementing this type of syntax is not a privilege of the implementation alone. You're allowed to redefine your own reader for left paren. In SBCL:

    CL-USER> (get-macro-character #\()
    SB-IMPL::READ-LIST
You can write `(set-macro-character #\( 'sb-impl::read-list)` and everything continues to work just fine. You can also jump-to-source and modify it if you want -- though it's cleaner to just copy it out to your own project, that's what I did for a quick hack/proof of concept. Essentially I added before the existing (when...) which handles the special dot syntax:

      (when (and (eq firstchar #\-)
                 (eq (peek-char t stream t nil t) #\>))
        (read-char stream t) ; actually read the nextchar > to discard it
        (let ((next-obj (read stream)))
          (sb-impl::flush-whitespace stream rt)
          (return `(slot-value ,@listtail ',next-obj))))
I won't claim this is good or proper, but it shows that it's quite feasible. We've turned (foo -> bar) into (slot-value foo 'bar).

    CL-USER> (defclass vec2 ()
      ((x :initarg :x)
       (y :initarg :y)))
    #<STANDARD-CLASS COMMON-LISP-USER::VEC2>
    CL-USER> (defparameter vec (make-instance 'vec2 :x 3 :y 4))
    VEC
    CL-USER> (vec -> y)
    4
    CL-USER> (read-from-string "(print (vec -> x))")
    (PRINT (SLOT-VALUE VEC 'X))
    18
Personally I wouldn't use this even if it was more properly/carefully implemented. (There's really no reason to replace the default left-paren reader, and no reason we have to have a space surrounding the "->". One thing I like about the infix reader macro package https://github.com/quil-lang/cmu-infix is that it doesn't care about spaces, I can write #I(1+1 + 4) and get 6.) I'm quite happy putting my class in its own package, and thus getting the primary tab-completion behavior I care about. e.g. "(ma:<tab>" could complete to "(math:" and then "(math:v<tab>" could complete to a list of options like "vector-x" "vector-y" or so on. I also like the somewhat unusual approach of naming my accessors with a dot prefix, e.g. (.x vec) and (.y vec), or even (math:.x vec) if I haven't imported the symbol.
1 comments

Good things are worth waiting for. I never considered making a reader macro for a regular opening bracket, that's equal parts genius and insanity.