Friday, May 15, 2009

Syntax that cheats and a lesson for R7RS

Talk to any schemer and they will extol the value of prefix notation. The conversation might go like this:

Non-Schemer> Prefix notation is ugly

Schemer> What are you talking about? How are these two different? foo(a, b) and (foo a b)

Non-Schemer> Well, what about operators?
Schemer> It takes some practice, but having (+ a b c) means that your expressions are never ambiguous. Besides you can read the above as "add a b and c" - seems readable to me.

Non-Schemer> Oh yeah, what about conditional operations like <

Schemer> Oh you just don't get it, do you?

And the conversation goes downhill from there. But this last point is a good one. Even with years of Scheme practice, I find:

  (< i j)
clunkier than I would like to admin. The problem, partially, may have to do with the fact that < isn't so much a word as it is a graphic. Combine that with the fact that it's read as "less than i j" -- and you can see that it's not that natural.

A novel solution I've seen recommended is to do the following:

 (define increasing? <)
 (when (increasing? a b)
  (printf "~a is less than ~b\n" a b))

Cool, but then I have to think does increasing? correspond to < or >?

And then, yesterday, I saw a pretty solution to this. For some time, I've known that PLT Scheme offers infix type notation that allows you to say:

 (i . foo . j)

Which automatically is mapped to: (foo i j) (notice the two dots).

Well, it never occurred to me to use this for conditionals. I can now write:

 (cond [(i . < . j) ...]
       [else ...])

And the conditional reads just like I'm used to.

Now I have to decide, do I stick with the standard Scheme or give in to this nifty syntax? Oh, decisions, decisions.

A Lesson For R7RS

So what does this all have to do with R7RS, the next scheme standard? Well, I'm certainly not suggesting that this notation become standard. But I would say this feature captures part of the equation for a successful scheme standard.

Mainly, there are two goals that come to mind for R7RS:

  • Make it as brief as possible. Let's face it, the R5RS standard is a work of art. Compact enough to print and read, yet powerful enough to build great systems.
  • While it's easy to side for minimalism, try using PLT-Scheme for some time, and you'll quickly learn to appreciate the extensive set of features it offers. The infix notation is just one example of a cool feature that makes production programming more effective in a non-standard scheme.

So how do you reconcile these two, apparently contradictory goals? So far, my personal litmus test for a successful R7RS is as follows:

  1. Is it brief? Have we removed every possible feature that could possibly be removed?*
  2. Does it provide a path for developers to add the sophisticated features that a PLT-Scheme and other robust implementations offer? For example, to implement the infix notation above, R7RS may offer the ability to alter the reader, and leave it up to the developer to actually make such a heretical decision as to allow for infix notation.

If it can pass those two tests, R7RS will get my vote. Not that I won't change my mind by the time it's written.

Now I have to go off and ponder the value of infix versus prefix notation...

*This remove all functionality philosophy is standard in the schemers world. I wonder if the standards process should embrace this and specifically have a feature removal stage/vote? Just a thought.

11 comments:

  1. I prefer (infix i < j) since I think it is clearer and my macro is portable between Scheme implementations.

    That said, I agree that Scheme should have standard ways to enhance the reader.

    ReplyDelete
  2. Hey Robert -

    Thanks for sharing. I would probably have less guilt using (infix i < j).

    It seems like it would be a trivial macro to write - I wonder why I hadn't ever though of writing it?

    Thanks for sharing!

    ReplyDelete
  3. Well, considering it was one of first macros I ever tried to write and it only took me like 10 minutes, I’d think it’d be easy for a more experienced Schemer.

    Of course, it can get non-trivial. Mine doesn’t implement precedence, which some people might consider a requirement of an infix macro. (Even in languages with infix notation I tend to fully parenthesize expressions rather than relying on precedence.) Also, I didn’t provide a way to escape back into prefix notation.

    FWIW, here it is:

    (define-syntax infix
    (syntax-rules ()
    ((_ (x . y))
    (infix x . y))
    ((_ x op y)
    (op (infix x) (infix y)))
    ((_ op x)
    (op (infix x)))
    ((_ x)
    x)))

    ReplyDelete
  4. Anonymous7:24 PM

    Minor quibble, but I read (+ a b c) as "the sum of a, b and c."

    ReplyDelete
  5. Robert - Thanks for sharing the code.

    Matt - you're version definitely makes you sound smarter than my version ;-)

    I'll keep that in mind for future reference.

    ReplyDelete
  6. Ben, I went through the same thing coding some trig computations a few weeks ago... from (- a b) to (a . - . b) to (infix a - b) to [a-b].

    I wrote a reader macro that handles all the C operators using a shunting-yard parser. It's not simple or portable but it does exactly what I want. I'll upload it to PLaneT once it's cleaned up and tested enough.

    ReplyDelete
  7. Ted Henry2:48 AM

    You just don't get it, do you? ;-)

    ReplyDelete
  8. Ted -

    I'll bite, don't get what?

    -Ben

    ReplyDelete
  9. Infix lisp isn't a real lisp.

    ReplyDelete
  10. guys with a problem with:

    (< a b)

    are the same with a problem with:

    (sort < ls)

    there are no operators in Lisp, only functions.

    Not worth the time fixing a 50-year old nice model of computation in favor of lesser developers who can't get their asses out of their C-heads. In trying to fix Lisp we could get ourselves inside far too much perlisms as with Haskell's ``, $, . etc

    ReplyDelete
  11. Well, we didn't remove everything from R7RS that we could have, but neither does R5RS, not by a long shot. Why not write your own COND and LENGTH, for example? Why have them in the standard? So that (a) you don't have to write them yourself, and (b) everybody doesn't have to adapt to your slightly different implementation; if your code uses the standard implementation, it's readable automatically.

    R7RS has grown by about 50% in terms of pages. I think that's reasonable, considering how much additional function it standardizes.

    ReplyDelete