Arc Forumnew | comments | leaders | submitlogin
Make 't, 'nil and number symbols rebindable?
4 points by evanrmurphy 4062 days ago | 8 comments
Currently, ac treats 't, 'nil and symbols corresponding to numbers (e.g. '2) as special cases where rebinding is prohibited.

  arc> (= t 1)
  Error: "Can't rebind t"
  arc> (= nil 1)
  Error: "Can't rebind nil"
  arc> (= 2 1)
  Error: "Can't take car of 2"
Obviously rebinding these symbols is a big deal, but should it really be prohibited? The restriction would seem to be an instance of trying to protect the programmer from him/herself, which Graham expresses wanting to avoid in "Some Work on Arc" [1]:

> [...] the third type of restriction, the kind intended merely to keep the user from getting into trouble, would be encapsulation. There won't be any of this type of restriction in Arc, if I can help it. There might (or might not) be situations where this kind of restrictive language would be a net win, but it's not the kind of language I'm trying to write.

It is also inconsistent with the way other symbols fundamental to Arc are allowed to work, for example 'car [2]:

  arc> (= car 'foo)
  arc> car
So why not treat 't, 'nil and number symbols the same way, as symbols initially bound to their important meanings but still rebindable? Here is the behavior I would like to see (demonstrated at the Arc REPL of my dreams):

  arc> (= t 'foo nil 'bar 2 'baz)
  arc> `(,t ,nil ,2)
  (foo bar baz)
Maybe their original values could be restored with something like the following:

  arc> (= t   (is 'x 'x)
          nil (is 'x 'y)
          2   (+ 1 1))
  arc> `(,t ,nil ,2)
  (t nil 2)
But even if they couldn't be, it doesn't undermine the main point, which is that these symbols should be rebindable despite that it's dangerous in order to be in line with Arc's design philosophy. (That is, unless there's some other reason not to allow rebinding of these symbols, in which case please tell me!)


[2] aw pointed this out in a related subthread about the (un)rebindability of 't:

5 points by fallintothis 4061 days ago | link

It's entirely possible. Here's a proof-of-concept patch that seems to accomplish most of the t and nil rebinding.

  $ diff -u old-ac.scm new-ac.scm
  --- old-ac.scm  2010-05-03 09:04:07.000000000 -0700
  +++ new-ac.scm  2010-05-03 10:11:33.000000000 -0700
  @@ -17,7 +17,9 @@
   (define (ac s env)
     (cond ((string? s) (ac-string s env))
           ((literal? s) s)
  -        ((eqv? s 'nil) (list 'quote 'nil))
  +        ((eqv? s 'nil) (if (bound? 'nil)         ;XXX
  +                           (ac-var-ref 'nil env)
  +                           (list 'quote 'nil)))
           ((ssyntax? s) (ac (expand-ssyntax s) env))
           ((symbol? s) (ac-var-ref s env))
           ((ssyntax? (xcar s)) (ac (cons (expand-ssyntax (car s)) (cdr s)) env))
  @@ -396,8 +398,8 @@
     (if (symbol? a)
         (let ((b (ac b1 (ac-dbname! a env))))
           (list 'let `((zz ,b))
  -               (cond ((eqv? a 'nil) (err "Can't rebind nil"))
  -                     ((eqv? a 't) (err "Can't rebind t"))
  +               (cond ;((eqv? a 'nil) (err "Can't rebind nil"))
  +                     ;((eqv? a 't) (err "Can't rebind t"))
                        ((lex? a env) `(set! ,a zz))
                        (#t `(namespace-set-variable-value! ',(ac-global-name a)
  @@ -747,7 +749,7 @@
   (xdef is (lambda args (pairwise ar-is2 args)))

   (xdef err err)
  -(xdef nil 'nil)
  +;(xdef nil 'nil)
   (xdef t   't)

   (define (all test seq)
The ;XXX in ac marks code that special-cases nil so as previous uses don't break -- i.e., so I didn't have to change every nil to 'nil in arc.arc et al. For example, the literal nils in map1 and so forth:

  arc> (map1 [+ _ 1] (list 1 2 3))
  (2 3 4)                         
  arc> (if nil 'a 'b)
  arc> (if 'nil 'a 'b)
  arc> (= nil 'blah-blah-i-am-true-blah)
  arc> (map1 [+ _ 1] (list 1 2 3)) ; literal nil still treated like empty list
  (2 3 4)
  arc> (if nil 'a 'b)
  arc> (if 'nil 'a 'b)
It's a pretty horrible solution:

  $ rlwrap mzscheme -f as.scm
  arc> (def f (x) (cons x nil))
  #<procedure: f>
  arc> (f 5)
  arc> (= nil 10)
  arc> (f 5)
  arc> (def g (x) (cons x nil))
  #<procedure: g>
  arc> (g 5)
  (5 . 10)
But as I said, just a proof of concept.

More importantly, do you actually have a reason you want to rebind the canonical false value? I mean, basically all it could do is break. At least t is only boolean true because it's a symbol other than 'nil. Even with the special case above, you have macro issues:

  arc> (if (or (> 1 2) (> 3 4)) 'a 'b)
  arc> (= nil "At a certain point, this just gets silly...")
  "At a certain point, this just gets silly..."
  arc> (if (or (> 1 2) (> 3 4)) 'a 'b)
Unless you gut Arc to use 'nil (or something else) as its false/empty-list value, you'll break anything that uses literal nils. And after gutting, all you gain is, what, the ability to use one more variable name? I think that's a different sort of restriction than being unable to redefine built-in functions.

Essentially, your suggestion is to treat literals as variables. But that's the point of nil and 2 and #\a and "this is a string": they're how you spell the basic constants out of which you build programs. I could philosophize about whether users should have the "freedom" to change what 2 means, but it devolves into the stuff of jokes (


1 point by evanrmurphy 4061 days ago | link

Thanks for the excellent responses by yourself and waterhouse. I'll likely come back to this discussion several times in the near future (I want to try your patch), for now just a brief reply:

> More importantly, do you actually have a reason you want to rebind the canonical false value? I mean, basically all it could do is break. At least t is only boolean true because it's a symbol other than 'nil.

I don't have particular reason to redefine canonical false yet, but I did like the implications of all this on numbers for alists and would be very curious to hear reactions to that - maybe the idea is flawed or I need to explain it better [].

However, even if there were no immediate need to redefine 'nil, numeric symbols etc., I detect an ugliness with their redefinition being prohibited. I simply do not understand why ints would be untouchable when 'car, 'cdr, 'eval etc. - the true axioms of the language - can be reassigned in a heartbeat. If it has to be that only some of these symbols are sacred, I think it should be the axioms, not some literals. (Admittedly, there is some overlap, especially in 'nil territory.) Otherwise, it'd be cleaner to make everything rebindable or nothing so, and the former seems more in the spirit of Arc.


2 points by fallintothis 4061 days ago | link

I detect an ugliness with their redefinition being prohibited.

But there's still a conceptual difference: numbers, strings, and characters are not variables to be redefined, but are syntax for constant values -- just like parentheses are syntax for "begin a list" and "end a list". It's like saying you should be allowed to change how the word "hello" is spelled.

Likewise, Arc works to make nil a literal value, (mostly) like a character or a string. Thus, redefining it is nonsensical. t isn't really a literal value, because any non-nil value is boolean true anyway. So, I agree that treating t as a variable could be allowed without breaking the current conceptual model.

I did like the implications of all this on numbers for alists and would be very curious to hear reactions to that

The reason 2 is the same as '2 isn't so much that quote special-cases numbers, it's that quote delays evaluation (which doesn't really happen for 2). On its own, foo evaluates to whatever it's bound to, so 'foo delays that. Compare with 2, which is "self-evaluating" -- as soon as we read it in, we know it's the number 2. Then '2 delays the evaluation of syntax that already had meaning before quote touched it. Hence, '2 is still just the number 2. Similarly for '#\a and '"a string".

You could make quote a special-casing operator, so '2 is different from 2 just because it's useful, but that doesn't let you redefine 2.

You could also change Arc so that there would be a layer between reading 2 and going "hey, that's the number 2". Then '2 would be more sensible and each 2 in your code could be a lookup to some value defaulting to...well, the number 2, I guess. But how do you distinguish 2-the-numeric-value and 2-the-variable if they're both spelled the exact same way? In the face of redefinition, (is (+ 1 1) 2) isn't an identity that would hold. What about extending this to less inductively-defined data types? How do you change #\a back to normal after (= #\a #\b)? Does it make (is "abc" "bbc") true? What if you first did (= "abc" "foo")?

The idea isn't just different, it creates a radically new language. That doesn't mean it's without merit. Off the top of my head, Common Lisp has reader macros that allow some syntax to be redefined, and PLOT ( lets you redefine syntax dynamically, though I'm not sure to what extent. But it's arguably one the first types of restrictions mentioned in (I don't mean to appeal to authority; it could just explain why Arc doesn't do this).


3 points by waterhouse 4062 days ago | link

The way ac.scm is currently implemented, it doesn't even matter if you rebind the symbol nil:

  arc> (mz (set! _nil 'ach))
  arc> nil
This is because nils are turned directly into 'nils:

  (define (ac s env) ;ac.scm
    (cond ((string? s) (ac-string s env))
          ((literal? s) s)
          ((eqv? s 'nil) (list 'quote 'nil))
Regarding numbers (and strings, you mention later): Well, why not allow '( to be a symbol as well? Because parentheses are special characters, and the reader interprets them as such. If you really want to make a symbol using special characters that the reader will recognize, you can escape the characters, using backslashes or vertical bars:

  arc> (= i\'m-a-symbol 2)
  arc> (+ |i'm-a-symbol| 3)
  arc> (list 'bob\ the\ hun '\( '\ )
  (|bob the hun| |(| | |)
Numbers are special characters, in this way: if an entire token consists of numbers (or "<numbers>.<numbers>", and various other number formats), then the reader treats it as a literal number. But you can escape the numerical characters and bind the symbol 2 if you want:

  arc> (= \2 3)
  arc> (+ \2 4)
  arc> '\2
Escaping non-special characters seems generally to do nothing:

  arc> (is 'ach '\a\c\h '|ach|)
So, really, you can already bind the symbols 2, "meh", and so on. It's just that you need to escape them, or else the reader will interpret them as literal numbers and strings and so forth. (By the way, the Arc compiler still interprets the characters ~.&: as ssyntax even if you escape them. That is most likely a bug, rather than a conscious design decision.)

In light of the above, and aw's example of the expression (let nil 3 ...) vs (let () 3 ...), it seems like either the reader is treating nil as a special case and turning it into (), or the whole macro-expansion environment is treating the symbol 'nil specially (not the value of the symbol nil, but the symbol 'nil itself). In fact, we can see in ac.scm that the latter is the case. Either way, handling of 'nil happens before the value of the symbol even starts to matter. (And hey, that's basically what I said at the top!) It seems that if you want (= nil something-else) or (let nil something-else) to have any noticeable effect, then you can't have (let () ...) mean the same thing as (let nil ...) anymore. I don't like that option. I like having nil be a literal empty list.

However, I could see this being done: have nil be escapable, so that |nil| is the symbol you can rebind, while nil is the literal empty list. ...And then are they eq? If they were, then macros passing around |nil| would probably blithely turn it into nil. If not, then... then this official nil symbol is not the same thing as a symbol constructed from the characters n,i,l, which breaks my mental model of the universe. So I guess being able to say (let nil ...) just affords nil special status. (I suppose 'let could very well treat the symbol 'bob specially as well--but no, because let is really skin on top of fn, which is a fundamental part of the language.) I think I'm happy requiring that nil be un-rebindable.

Regarding t: I am all for allowing local rebinding of t. It's already possible to (let t 2 t), just not to follow it with (++ t), which seems pretty clearly to be a bug. I also agree with allowing global rebinding of t; it really is just a symbol, and even though it does show up in the primitives ('is and 'atom choose to return 't as their truth value), they could just return 't rather than t (i.e. quote the symbol), and then t wouldn't have to have a value at all. It is simply for convenience that we effectively have (= t 't). So, yes, t should be rebindable, and I have accordingly modified my ac.scm; just note that if you do rebind t, then everything under the sun is liable to stop working, though less so than if you rebind car:

  arc> (= car nil)
  arc> (= a 2)
  Error: "Function call on inappropriate object nil ((a 2))"


2 points by evanrmurphy 4061 days ago | link

The escaping system is more robust than I realized. I think some of my concerns are definitely addressed with being able to assign escaped numerical characters, etc.

> However, I could see this being done: have nil be escapable, so that |nil| is the symbol you can rebind, while nil is the literal empty list. ...And then are they eq?

I looked into this and think it's kind of elegant now. 'nil is unescapable like non-special character symbols in general are [1]:

  arc> (is 'nil '\n\i\l '|nil|)    ; like your 'ach example
But () is escapable (as special characters typically are):

  arc> (= |()| "escaped empty set")
  "escaped empty set"
Like typical escaped characters, it isn't eq to nil and () unless you assign it that way:

  arc> (is () |()|)
  arc> (= |()| nil)
  arc> (is nil () |()| \(\))
[1] I was frustrated momentarily when I couldn't escape 't until I realized it was the same way. As you pointed out, at least it is locally rebindable by default. And then I suppose 't isn't an especially desirable global variable name anyway.


1 point by evanrmurphy 4062 days ago | link

Another reason it makes sense to decouple numbers in particular from their symbols is that it indirectly solves the '! vs. 'alref problem we've had with association lists (another valient hack by aw []). Basically the problem has been, how do you cleanly implement !-alrefs for alists when there is the special case of al!n if n is a number? [] While it's tempting to think this points to an issue with using '! for alists, in my mind the issue actually roots itself with numbers being inseparable from their symbolic representations. Only for numbers does 'n evaluate to n unconditionally, and as soon as you let them be independent, then everything falls into place with al.n returning the nth cadr of al as it always does for lists and al!n now able to do an alref on the symbol 'n.


1 point by evanrmurphy 4062 days ago | link

I like aw's localt0 hack [] but can't (yet) see any reason other than compromise not to allow reassignment of 't and the other symbols even at the global level.

He brings up an interesting point about 'nil though, reminding that it's equivalent to the empty list. I could be mistaken, but it seems as though Arc makes 'nil the more fundamental of the two. My evidence for this is that '() evaluate to nil at the REPL, rather than the reverse:

  arc> 'nil
  arc> '()
I say it should go the other way around, with 'nil evaluating to (). After all, if any symbol in Lisp is sacred it's parens, not the word nil. But more relevant to the point, this frees 'nil up to be a settable symbol that's only initially bound to the empty list.


1 point by evanrmurphy 4062 days ago | link

If my argument holds water, then I think it has implications for strings as well, but I don't know what they are yet.