Hacker News new | past | comments | ask | show | jobs | submit login

Thanks for the clarification on the meaning of "literal" in Common Lisp, I'll try to keep that in mind in the future. My meaning was more in the sense of literals being some textual equivalent representation for a value. Whether or not computation behind the scenes happens at some particular time (read/compile/run) isn't too relevant. For example in Python, one could write:

    a = list()
    a.append(1)
    a.append(2)
    a.append(1+3)
You can call repr(a) to get the canonical string representation of the object. This is "[1, 2, 4]". Python's doc on repr says that for many object types, including most builtins, eval(repr(obj)) == obj. Indeed eval("[1, 2, 4]") == a. But what's more, Python supports a "literal" syntax, where you can type in source code, instead of those 4 lines:

    b = [1, 2, 1+3]
And b == a, despite this source not being exactly equal at the string-diff level to the repr() of either a or b. The fact that there was some computation of 1+3 that took place at some point, or in a's case that there were a few method calls, is irrelevant to the fact that the final (runtime) value of both a and b is [1, 2, 4]. That little bit of computation of the element is usually expected in other languages that have this sort of way to specify structured values, too, Lisp's behavior trips up newcomers (and Clojure's as well for simple lists, but not for vectors or maps).

Do you have any suggestions on how to talk about this "literal syntax" in another way that won't step on or cause confusion with the CL spec's definition?






> Whether or not computation behind the scenes happens at some particular time (read/compile/run) isn't too relevant.

Actually it is relevant: is the object mutable? Are new objects created? What optimizations can a compiler do? Is it an object which is a part of the source code?

If we allow [1, 2, (+ 1 a)] in a function as a list notation, then we have two choices:

1) every invocation of [1, 2, (+ 1 a)] returns a new list.

2) every invocation of [1, 2, (+ 1 a)] returns a single list object, but modifies the last slot of the list. -> then the list needs to be mutable.

    (defun foo (a)
      [1, 2, (+ 1 a)])
Common Lisp in general assumes that in

    (defun foo (a)
     '(1 2 3))
it is undefined what exact effects the attempts to modify the quoted list (1 2 3) has. Additionally the elements are not evaluated. We have to assume that the quoted list (1 2 3) is a literal constant.

Thus FOO

* returns ONE object. It does not cons new lists at runtime.

* modifying the list may be not possible. A compiler might allocate such an object in a read-only memory segment (that would be a rate feature -> but it might happen on architectures like iOS where machine code is by default not mutable).

* attempts to modify the list may be detected.

SBCL:

    * (let ((a '(1 2 3))) (setf (car a) 4) a)
    ; in: LET ((A '(1 2 3)))
    ;     (SETF (CAR A) 4)
    ; 
    ; caught WARNING:
    ;   Destructive function SB-KERNEL:%RPLACA called on constant data: (1 2 3)
    ;   See also:
    ;     The ANSI Standard, Special Operator QUOTE
    ;     The ANSI Standard, Section 3.7.1
    ; 
    ; compilation unit finished
    ;   caught 1 WARNING condition
    (4 2 3)
* attempts to modify literal constants may modify coalesced lists

for example

    (defun foo ()
      (let ((a '(1 2 3))
            (b '(1 2 3)))
        (setf (car a) 10)
        (eql (car a) (car b))))
In above function, a file compiler might detect that similar lists are used and allocate only one object for both variables.

The value of (foo) can be T, NIL, a warning might be signalled or an error might be detected.

So Common Lisp really pushes the idea that in source code these literals should be treated as immutable constant objects, which are a part of the source code.

Even for structures: (defun bar () #S(PERSON :NAME "Joe" :AGE a)) -> A is not evaluated, BAR returns always the same object.

> Do you have any suggestions on how to talk about this "literal syntax" in another way that won't step on or cause confusion with the CL spec's definition?

Actually I was under the impression that "literal" in a programming language often means "constant object".

See for example string literals in C:

https://wiki.sei.cmu.edu/confluence/display/c/STR30-C.+Do+no...

Though it's not surprising that language may assume different, more dynamic, semantics for compound objects like lists, vectors, hash tables or OOP objects. Especially for languages which are focused more on developer convenience, than on compiler optimizations. Common Lisp there does not provide an object notation with default component evaluation, but assumes that one uses functions for object creation in this case.


Yeah, again I meant irrelevant to those who share such a broader ("dynamic" is a fun turn of phrase) definition of "literal" as I was using, it's very relevant to CL. I thought of mentioning the CL undefined behavior around modification you brought up explicitly in the first comment as yet another reason I try to avoid using #() and quoted lists, but it seemed like too much of an aside in an already long aside. ;) But while in aside-mode, this behavior I really think is quite a bad kludge of the language, and possibly the best thing Clojure got right was its insistence on non-place-oriented values. But it is what it is.

Bringing up C is useful because I know a similar "literal" syntax has existed since C99 for structs, and is one of the footguns available to bring up if people start forgetting that C is not a subset of C++. Looks like they call it "compound literals": https://en.cppreference.com/w/c/language/compound_literal (And of course you can type expressions like y=1+4 that result in the struct having y=5.) And it also notes about possible string literal sharing. One of the best things Java got right was making strings immutable...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: