Accidental Syntax

Mark-Jason Dominus writes about "accidental syntax" in Perl, and wonders if this happens in other languages. I couldn't think of any examples offhand, but I'm sure Common Lisp must have some...

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Duff's device qualifies

Duff's device has accidental semantics, too!

Quasiquotation

The semantics of the quasiquotation operators can get pretty hairy. ISTR a classic incident when the code was right, but it took the gurus some time to realize why (It involved something like `,`, ).

Nested quasiquotation

I think that the semantics of nested quasiquotation in Common Lisp and Scheme is wrong. It should work like backslashes in strings in most languages: unescaped unquotes are processed immediately, escaped unquotes become unescaped unquotes for the next stage.

This semantics wouldn't need the current workaround for the inability of unquoting inside a nested quasiquotation. The workaround uses a regular Lisp quote, and leaves an extra unquote and quote after the first stage of the expansion, which cancel each other out under the standard interpretation of quasiquotation (modulo sharing constraints).

A concrete syntax of escaping might unambiguously use doubled unquote for example to denote escaped unquote, as a naked unquoted term is not a valid expression, so this couldn't be meaningfully interpreted as a regular unquote.

Exponential growth of backslashes

You can get a double quote in a string by quoting it: "\"" => "

If you want to quote the source string you have to backslash the quotes and the backslash: "\"\\\"\"" => "\""

Another round of quoting: "\"\\\"\\\\\\\"\\\"\"" => "\"\\\"\""
make the exponential growth frighteningly apparent.

By constrast, CL's nested quasiquotation works OK. Here is an example of a triple nesting

(defmacro define-namespace (prefix-to-name operator)
  `(defmacro ,(intern (format nil "~A-NAMES" prefix-to-name)
                      (symbol-package prefix-to-name))
    (names &body code)
    (flet ((make-macro-def(name)
             `(,name (&rest args) `(,',',operator ,',name ,@args))))
      `(macrolet ,(mapcar #'make-macro-def names) ,@code))))

The variable OPERATOR is evaluated on the first round of evaluation, then sits out two more rounds.

Nesting is shallow

I doubt that anyone needs more than 3 levels of nesting in practice. With even deeper nesting it's probably more clear to use auxiliary macros than to write everything inline, no matter which semantics of nesting is chosen.

And with 3 levels of nesting the exponential length is quite short compared to the linear length: , and ,, and ,,,, correspond to ,',', and ,', and ,.

Gabriel & Steele

There's a discussion of comma-quote-comma on p74 of The Evolution of Lisp. It is intuitive when you've got your head around it, but it took me a while. Definitely accidental syntax.

Does ^infix^ notation in C++ count?

For example from this page describing its use in the fc++ library.

Contortions++

But I mean the subject in a good way. Reading this, and reading about some of the features in the Boost C++ -- especially the lambda and format facilities -- I've lately seen some interesting C++ constructs. Impressive, actually, as it allows for a more expressiveness than I thought possible for the language.

That looks like the kind of

That looks like the kind of hoops jMock makes Java jump through to emulate Smalltalk syntax.

C's "goes toward" operator

yesterday I stumbled over the "goes toward" operator:


        void doStuff(int count) {
                while(count --> 0) 
                        fleh();
        }

It is possible to make

It is possible to make this

Vector vec;
vec = 1, 2, 3;

initialise a vector with 3 elements. But it is parsed as

((vec = 1), 2), 3;

Vector vec = 1, 2, 3;

will not work.

This is really where

This is really where operator overloading gives me a headache. It's a perfect example of accidental syntax, I think, taking advantage of the fact that assignment in C++ is an expression, as is the result of the comma (list) operator. It doesn't work in the Vector vec = ... example because you're declaring a variable, and in that context the comma takes on a different meaning (allowing you to declare multiple variables of one type: Vector v = 1, vv, vvv = 3, and so forth). Any other operator overload should work, except for the comma, which here loses its usual expressive purpose.

I do think operator overloading can be useful and can be done well. But it's up to the designer whether it is or is not done well, which puts off a lot of people. Some would rather throw up their hands in disgust, and have the whole lot carted off to the trash.

Jensen's device in Algol 60

Wikipedia has a description of it.

Jensen's Device and Man or Boy Test in SML

For fun, I transliterated Jensen's Device and (Knuth's) Man or Boy Test to SML. Do explicit ref cells and thunks make them easier to understand?

(* Jensen's Device *)
local
   val i = ref 0
   fun sum (i, lo, hi, term) =
       let
          val temp = ref 0.0
       in
          i := lo
        ; while !i <= hi do
             (temp := !temp + term () ; i := !i + 1)
        ; !temp
       end
in
   val () = print (Real.toString (sum (i, 1, 100, fn () => 1.0 / real (!i)))^"\n")
end

(* Man or Boy Test *)
local
   fun A (k, x1, x2, x3, x4, x5) =
       let
          fun B () =
              (k := !k - 1
             ; A (ref (!k), B, x1, x2, x3, x4))
       in
          if !k <= 0 then x4 () + x5 () else B ()
       end
in
   val () = print (Int.toString
                      (A (ref 10,
                          fn () => 1,
                          fn () => ~1,
                          fn () => ~1,
                          fn () => 1,
                          fn () => 0))^"\n")
end

Never understood why that has a special name

It just seems like what you'd expect from call-by-name. Surely the designers of Algol intended this, otherwise they'd have adopted call-by-value.

No, from that I heard the

No, from that I heard the designers of Algol 60 didn't have a very
thorough understanding of what could be done by call by name, and jensens device was concived to point that out. I don't even think that it could be run on many implementations of Algol 60.