Arc Forumnew | comments | leaders | submitlogin
Possible bug in coerce with quoted nils?
2 points by dido 2647 days ago | 14 comments
I've been playing around with arc3 and noticed some behavior that seems a little odd. The symbol nil appears to behave specially even when it's quoted:

arc> (coerce nil 'string)


arc> (coerce 'nil 'string)


Shouldn't the second line say "nil" instead, since the nil is quoted?

It seems that Arc3 handles the case of quoted nils correctly in other circumstances:

arc> (cons 'a (cons 'nil nil))

(a nil)

2 points by rocketnia 2647 days ago | link

The fact that Arc's implementation special-cases nil and () does make it quirky, but in these examples I think it's totally fine. IMO, nil should be seen as a variable that has been initialized to the symbol named "nil". (Arc actually treats it a bit more like a literal expression than a variable, and it makes an unnecessary and mostly invisible distinction between nil and ().)

Your "handles the case of quoted nils correctly" example doesn't have to do with the nil being quoted. Here's the same thing with it unquoted:

  arc> (cons 'a (cons nil nil))
  (a nil)
Since nil is a variable holding the symbol named "nil", the symbol named "nil" appears in the output, just as if you'd quoted it.

Now for why (coerce nil 'string) is "". A long time ago,[1], I used to think it made more sense to have (coerce nil 'string) be "nil", so that you could always get the name of a symbol without resorting to things like [string:or _ "nil"]. However, now I realize it's a pretty common operation to build a list of strings and concatenate the whole list at once, and Arc provides that operation through [coerce _ 'string]. Arc's just favoring one useful functionality over another here.

[1], with a follow-up at


2 points by zck 2647 days ago | link

It makes for some interesting coercions:

  arc> ''nil
  (quote nil)
  arc> ''arst
  (quote arst)
  arc> (coerce ''nil 'string)
  arc> (coerce ''arst 'string)
I could see this being somewhat useful for printing things where nils might be involved:

  arc> (with (indiana (obj hat 'fedora weapon 'whip)
         vader (obj hat 'helmet weapon 'lightsaber)
         bond (obj weapon 'walther-ppk))
        (coerce (map [_ 'hat]
                     (list indiana vader bond))
Of course, you can easily remove 'nil from a list of things:

  arc> (with (indiana (obj hat 'fedora weapon 'whip)
         vader (obj hat 'helmet weapon 'lightsaber)
         bond (obj weapon 'walther-ppk))
        (trues idfn
               (map [_ 'hat]
                    (list indiana vader bond))))
  (fedora helmet)


1 point by shader 2641 days ago | link

For the most part, I don't mind this behavior. There's only one case where I with this was not the case, in which I was writing a compiler in arc which attempted to represent the ast as symbols in lists. Silly me, thinking that was the right way to represent an ast in arc.

The problem is that if someone were to use a variable in that language named nil, it would be represented in my ast as 'nil, which would then be treated in all of the rest of the code (including any parts which printed it out) as if it weren't there. I guess I should have used strings in all of those cases, but I was taking advantage of the fact that I could actually bind values to the symbols to store some metadata. It turned out to be a really handy way to work with the language, unless someone used 'nil as a variable. Doing anything else would have worked better, but been more verbose and hackish.

This is the only reason I wish that nil -> '() instead of nil = '(). Because |()| != '(), while |nil| = 'nil.


1 point by Pauan 2641 days ago | link

I think this should be configurable in ar, with the default being to follow Arc/3.1, but still allow the programmer to make nil different from 'nil.

Hey awwx, how hard would it be to make this change in ar right now? From what I can see, "nil" is just a global variable bound to the symbol 'nil. So, what if I did this...?

  (ail-code (racket-set! nil (uniq)))
It seems to work okay in the couple tests I did, but I wonder if it would break anything...


1 point by Pauan 2641 days ago | link

Hm... I just realized ar allows for rebinding nil and t! So you can just do this:

  (= nil (uniq))


1 point by Pauan 2641 days ago | link

By the way, if you ever decide to rebind nil, and it breaks stuff, you can fix things by doing this:

  (assign nil 'nil)
You need to use `assign` rather than `=` because `=` breaks if nil isn't 'nil.


1 point by rocketnia 2641 days ago | link

"I guess I should have used strings in all of those cases, but I was taking advantage of the fact that I could actually bind values to the symbols to store some metadata."

If you don't mind, how does/did it seem easier to bind values to symbols than to bind them to strings?


2 points by Pauan 2641 days ago | link

I'm not shader, but I'm guessing that it's because... you can't.... bind values to strings...?

I mean, yeah, you could have a table where the keys are strings, and treat that as the metadata, but uuugh it's clunky and I've found myself disliking the whole "global table to hold data" thing more and more, recently.

Then again, I don't know how their compiler works, so I might be completely off-base here...


2 points by akkartik 2647 days ago | link

'nil always evaluates to nil, it's a core assumption in scheme/arc. (You can see the specialcase baked into the top-level ac at ac.scm)

It's come up tangentially before: Waterhouse, can you summarize the benefits of 'nil evaluating to nil?


2 points by waterhouse 2646 days ago | link

[By now other replies make this somewhat redundant, but I'll proceed anyway. Much this post is rambling about types and language design.] I think dido's question can be answered more simply than that. I.e.: (1) nil is 'nil, always; (cons 'a (cons 'nil nil)) = (cons 'a (cons nil nil)). (2) The funny behavior you observe stems from this: The "coerce" function (and also the "string" function, which uses "coerce" quite directly) coerces the symbol 'nil to the empty string. This decision was made for convenience, as aw illustrates here:

To answer your question: It's more like a decision to make nil evaluate to 'nil. The train of reasoning goes like this:

1. We need a single value to be considered "false"--by the "if" special form and by any function that's supposed to return either true or false. We call this value nil. Now, we could invent a new type for nil (maybe a "false" type, or maybe a "boolean" type that is either true or false), but in Arc (and Common Lisp before Arc, and probably several other Lisps both historical and contemporary), nil is a symbol.[1]

2. You could stop here. Use 'nil whenever you want to return false. But it's kind of annoying to remember to type the quote. So, instead, we bind the global variable nil to the value 'nil. (The compiler actually handles nil directly--if you managed to rebind nil, it wouldn't make a difference, because nils are transformed away anyway.) Likewise, t is bound to 't, the canonical true value.

[1] Actually, in Common Lisp, nil has type "null", which is a subtype of "symbol", in the same way that a string is a subtype of an array. This might be worth considering...

  * (type-of nil) ;Common Lisp
  * (symbolp nil)
  * (type-of "ach")
  * (arrayp "ach")
  * (stringp "ach")
Additionally, in CL, the type of t is "boolean", also a subtype of "symbol". And, in fact, "null" is a subtype of "boolean". Hmmph. I feel that's a bit excessive. At any rate, it doesn't make much difference (you could always just ask (is x t)), except perhaps when you're giving type advice to the compiler--but if you were doing that, you should be able to define a "boolean" type as (or t nil), the way you'd define any other derived type. Perhaps nil should have a slightly special type, as it's treated specially by the primitives "if", "car", "cdr". [And hmm, could conses be fundamentally vectors of length 2, just with a different type tag? Probably. This is of course far afield of your question.]

Methinks "is-a" type semantics are fundamental for the implementation. Every object is some number of bits laid out in memory. Hmm, but then (listening to the part of my brain that generates objections anytime I talk as though I'm sure), there are things that could be represented in one of many ways, any of which might be best in a given situation. SICP gives the example of complex numbers as rectangular vs polar, and polynomials could be represented as lists or vectors of coefficients, alists or hash tables of (power coefficient), perhaps in factored form if applicable, and so on. (Even in my nil example, it could be a cons cell or a symbol.) Might it be bad, might it make it particularly difficult or inefficient to do that kind of abstract type behavior if the implementation just uses "is-a" semantics? Ehhh... no. :-) Type dispatching will be the answer, and it'll be just as easy/difficult either way.

[Comment: Any question of making objects callable in functional position is an efficiency concern. If you didn't care about efficiency, then you could just wrap every object inside a closure, and write the system's "print" and "type" functions and so forth to ask the closure how to properly print or type the object. ... And back to conses-as-vectors: I think you might want some arrays to have an entire field of tagging information that would probably need to be stored in the array. At the very least, you'd want the length. And I wouldn't want to make all cons cells 50% bigger. So, eh, conses will be special, and perhaps one could include boxes too... and maybe there's something to "weak" references, which exist but which the garbage collector is free to destroy. Actually, those could be implemented with boxes, though at the cost of adding an indirection to normal references to that object. Mmm.]


2 points by rocketnia 2646 days ago | link

"This decision was made for convenience, as aw illustrates here: "

"Conditionally include things in a string"

Sorry if I enter into a bit of drama here.... I don't have anything against you or aw, but every time I see that comment, something nags at me. :)

At the time, there were about six reasons I didn't respond to that comment (which was a reply to a comment of mine):

1) I didn't consider it to be a "brilliant use," since the workaround without it would be negligible:

  (string "You are going "
          (if (> speed 100) "very " "")
2) Were I to have accepted the brilliance of this use case, I'd only have been more interested in why 'pr didn't have the same behavior (which was my main gripe to begin with):

  arc> (= speed 2)
  arc> (prn "You are going " (if (> speed 100) "very ") "fast")
  You are going nilfast
  "You are going "
3) It was actually something I had already thought of and cast aside for the above reasons. (So it didn't even qualify as something "I'm not thinking of.") If aw was grasping for straws like I was, there probably wasn't much more point in talking about it.

4) It had upvotes too--at least one upvote at the time I first read it. Apparently, what I thought was common sense would be outnumbered.

5) I hadn't programmed in Arc that much yet. Maybe the upvoter(s) saw something there I didn't. At any rate, I'd probably be able to discuss the pros and cons better once I'd used Arc for a while.

6) I did say "I can't think of a single time," and this was a single example. I kinda decided to eat my words on account of that. :-p

Turns out (string:accum ...) is the use case I found that convinced me. The key point was that 'string wasn't consistent for symbols because it was too busy being consistent for lists.


1 point by aw 2644 days ago | link

Just to be clear :-), I wasn't advocating for the behavior, merely answering the question if was there any use for it ^_^


1 point by rocketnia 2644 days ago | link

Thanks for not taking that too harshly. XD


1 point by rocketnia 2646 days ago | link


My stance is that they're intuitively separate things. If you take the length of a vector of length 2, you get 2. If you take the length of a linked list node, that's a different story. Still, if a linked list node is implemented as a wrapper around a vector, that's fine.