Arc Forumnew | comments | leaders | submitlogin
1 point by Pauan 4720 days ago | link | parent

"Outside a macro it's less clear that this is what someone would want. [...] So maybe a "define-hygienic" macro could re-define quasiquote in this fashion - but elsewhere it would work as normal."

I don't see why... have you ever needed the normal quasiquote outside of macros? No? It's quite rare for me to want quasiquote outside of macros.

But the new quasiquote I'm proposing is useful outside of macros, precisely because it doesn't quote: it's a short version of list/cons:

  (cons a b)
  `(a . b)

  (list a b c)
  `(a b c)

  (list* a b c)
  `(a b . c)

  (list* a (list b c) d)
  `(a (b c) . d)
Then you could actually get rid of the "cons", "list" and list* functions and just use quasiquote instead.

---

"(list one two (list three))"

That is correct. If you want to express (list a b (c)) you can use `(a b ,(c))

---

"I had also realised that the "hygiene" is currently broken by the fact that, in FirstClass Lisp, "quasiquote", "quote", "unquote", "unquote-splicing", "dot" and "slash" are all "special" in terms of what the reader reads in, so something like the following:"

How I would handle this is to make the syntax expand to global values rather than symbols. So, for instance, I would define quote using vau[1] (or a macro, whatever):

  (define quote (vau _ (x) x))
And then I would have 'foo expand to (#<vau:quote> foo) where #<vau:quote> is the actual value of the global quote vau, not the symbol "quote". This avoids the redefinition problem you pointed out, because it doesn't use symbols, it uses values. Like I already said, I believe that any time you use symbols, you're hosed. Symbols are only useful for easily referring to values: it's the values that are important.

---

"If what you're saying is true then it might be possible to expand all of the "non" first class macros in a pre-compilation step anyway."

Various people (including rocketnia) are working on ways to inline vau, effectively turning them into compile-time macros. This is probably possible with the kind of immutable environments that Nulan has, but much much harder with mutable envs, because you need to rollback things if anything the vau depends on changes, as you already noted.

Personally, what I would do is provide full-blown Kernel-style vaus, including first-class environments:

  (vau env
    (a b c) (eval env `(...))
    _       (eval env `(...)))
Note that in the above version of vau, the environment argument comes first, and the remaining arguments are the pattern matching/body pairs, just like in your "lambda", except that it's a vau, obviously.

Then I would provide a way to tag a vau saying, "treat this vau like a macro". When the compiler sees something that's tagged as a macro, it will always macro-expand it at compile-time. Something like this:

  (define foo
    (macro (vau _
             (x y z) `(x y z)
             _       `())))
Of course, you should also define a "defmacro" or whatever to make the above shorter:

  (define defmacro
    (macro (vau env (name . bodies)
             `(define name
                (macro (vau _ . bodies))))))
        
  (defmacro foo
    (x y z) `(x y z)
    _       `())
 
I'd like to note that the above is a lot like the hygienic pattern matching macros found in Scheme... except it doesn't need a massively unbelievably horrifically bloated system: it's built on the very simple concept of tagging vaus with a type.

---

Depending on how you implement it, macros in the above system may or may not be first-class, but even if they're not first-class, they're still built on top of the first-class vau. So, most code would use macros for speed, but your amb could use a raw vau.

If macros were no longer first-class, then the performance of the program is now clearer: macros are always expanded at compile-time, so they're always as fast as macros in other Lisps.

But vaus are slower because they're always run at runtime, so this serves as a red flag that says, "hey man, this is slow!". This also suggests a simple rule: "never use a vau when a macro will do". This complements the rule of "never use a macro when a function will do".

---

I would also build "lambda"[2] on top of vau by using a tagging mechanism (like Kernel's wrap/unwrap):

  (defmacro lambda bodies
    `(wrap (vau _ . bodies)))
Basically, I believe the idea of tagging vaus to provide different callable behavior is extremely useful, and I prefer to see vau as the center of everything, with macros and functions being a subset of vaus (but with orthogonal purposes).

---

* [1]: I use the word "vau" to refer to "fexprs" because it's shorter, nicer looking, easier to say, it has an analog to "lambda", and it's what Kernel uses.

* [2]: Except I would call it "fn" rather than lambda, but that's just bike-shedding:

http://bikeshed.com/

http://en.wikipedia.org/wiki/Parkinsons_Law_of_Triviality



1 point by Pauan 4719 days ago | link

Oops, I accidentally put in an unnecessary "env". defmacro should be like this:

  (define defmacro
    (macro (vau _ (name . bodies)
             `(define name
                (macro (vau _ . bodies))))))

-----