Arc Forumnew | comments | leaders | submitlogin
On influencing evaluation order with function boundaries and ,@
1 point by akkartik 5176 days ago | 5 comments
I want a smart table initializer. Here's a spec (5 tests):

  (let a nil
    (test-iso "#1 - inittab returns table with values"
              (obj 1 2 3 4)
              (inittab a 1 2 3 4)))

  (let a (obj 1 2)
    (inittab a 3 4)
    (test-iso "#2 - inittab retains preexisting values"
              (obj 1 2 3 4)
              a))

  (let a (obj 1 2)
    (test-iso "#3 - inittab doesn't overwrite existing values"
              (obj 1 2 3 4)
              (inittab a 1 5 3 4)))

  (with (inittab-test nil x 0)
    (def inittab-test()
      (w/table ans
        (or= ans.x (table))
        (or= ans.x.3 4)
        (or= ans.x!foo (table))
        (or= ans.x!bar (table))))

    (with (a (table) x 3 y 'bar)
      (test-iso "#4 - inittab handles quoted and unquoted keys"
                ((inittab-test) 0)
                (inittab a.0 x (+ 3 1) 'foo (table) y (table)))))

  (with (a nil x 3)
    (inittab a x (+ x 1))
    (test-iso "#5 - inittab evaluates keys and values"
              (obj 3 4)
              a))
Here's my solution:

  (mac inittab(place . l)
    `(do
      (or= ,place (table))
      (init-table ,place ,@l)))

  (def init-table (table . data)
    (each (k v) (pair data) (or= (table k) v))
    table)
The only wart here is that the interface of init-table isn't like fill-table. It doesn't take just 2 args. My question is: can you think of a way to make inittab work if I make init-table look like fill-table?

  (def init-table2 (table data)
    (each (k v) (pair data) (or= (table k) v))
    table)
==

I've tried 2 approaches. The first is to try to avoid init-table by using eval. Eval'ing just values works fine:

  (mac inittab(place . l)
    `(do
      (or= ,place (table))
      (each (k v) (pair ',l)
        (or= (,place k) eval.v))
      ,place))
Test 5 will fail, and test 4 will pass if the final line contains literal keys:

  (inittab a.0 3 (+ 3 1) foo (table) bar (table))
However, taking this approach further with eval.k doesn't work.

  (mac inittab(place . l)
    `(do
      (or= ,place (table))
      (each (k v) (pair ',l)
        (or= (,place eval.k) eval.v))
      ,place))
Test 4: reference to undefined identifier: _foo

Test 5: reference to undefined identifier: _x

Eval not obeying lexical environments seems like a wart, but mzscheme doesn't seem to handle it either..

  mzscheme> (let ((k 3)) (eval 'k))
  reference to undefined identifier: k
  mzscheme> (define k 3)
  mzscheme> (eval 'k)
  3

  arc> (let k 3 (eval 'k))
  Error: "reference to undefined identifier: _k"
  arc> (= k 3)
  3
  arc> (eval 'k)
  3
So it seems we need that function boundary to do eval right.

==

Second approach. The obvious way to avoid the rest arg in inittab is as follows:

  (mac inittab(place . l)
    `(do
      (or= ,place (table))
      (init-table2 ,place ,l)))
But ,l now wants to evaluate as a function ("Function call on inappropriate object 1 (2 3 4)"), and if we quote it we won't get evaluation at all. Here's another approach:

  (mac inittab(place . l)
    `(do
      (or= ,place (table))
      (init-table2 ,place (apply list ',l))))
No dice. Tests 4 and 5 fail.

So it seems we need the ,@ operator to do eval right.

==

I'm reminded of Alan Kay's criticism of lisp's special forms:

"The pure language was supposed to be based on functions, but its most important components---such as lambda expressions quotes, and conds--were not functions at all, and instead were called special forms. Landin and others had been able to get quotes and cons in terms of lambda by tricks that were variously clever and useful, but the flaw remained in the jewel. Why on earth call it a functional language? Why not just base everything on FEXPRs and force evaluation on the receiving side when needed?" (http://gagne.homedns.org/~tgagne/contrib/EarlyHistoryST.html)



4 points by fallintothis 5176 days ago | link

You want

  (mac inittab (place . args)
    `(do (or= ,place (table))
         (init-table ,place (list ,@args))))

-----

1 point by akkartik 5176 days ago | link

Argh. Thanks!

-----

1 point by elibarzilay 5175 days ago | link

Fexprs are very different from macros. Not having them is considered by most people as a feature, not a wart. In fact, the common approach to fexprs is a wart. One implication is that eval, as implemented in MzScheme and in Arc is doing the right thing. (And macros are much more well behaved than fexprs, which keeps the language sane.)

-----

1 point by akkartik 5175 days ago | link

I don't follow. Are modern lisps not using f-exprs?

If so I have been imprecise. I was using Alan Kays quote to refer to lisp's special-forms and their many rules on what gets evaluated in what position when.

-----

2 points by elibarzilay 5174 days ago | link

No, modern lisps don't do that. (Except maybe for newlisp, which does what you'd call "the right thing" with `eval`, and even encourages doing so; but this goes with the fact that it throws lexical scope out the window.)

As for the quote, it depends on how you view it. You can take it anywhere to proper criticism of fexprs (which has become the popular view since then), or you can take it as criticizing the fact that special forms are needed, or if you squint hard enough, you can say that it's advocating a language like Haskell. Considering the first two options, I think that the first one (criticism of fexprs) is very explicit, the second one is less likely.

In any case, trying to get an `eval` that works with lexical scope is related to fexprs. So the feature that you want is one that Kay criticizes.

-----