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

    (list? '(1 2 3))
    ;;; => true
'(1 2 3) is, in fact, a list of 3 elements

    (= '(1 2 3) (cons 1 '(2 3)))
    ;;; => true

  = is closer to equal? in scheme. The doc string for clojure.core/= explains this pretty well:

    Equality. Returns true if x equals y, false if not.    Same as
    Java x.equals(y) except it also works for nil, and compares
    numbers and collections in a type-independent manner.  Clojure's immutable data
    structures define equals() (and thus =) as a value, not an identity,
    comparison.
Finally,

    (list? (cons 1 '(2 3)))
    ;;; => false
`cons` is defined over a sequence in Clojure, not a list. The doc string actually says this upfront "Returns a new seq ..." and the type of this happens to be a `clojure.lang.Cons`. To get add a value to a list, you would actually use `conj`, which conjoins a value onto a collection or sequence in a way that is specific to that collection or sequence.

    (list? (conj '(2 3) 1))
    ;;; => true
If you are dead-set on using lists for some reason (you probably want a different datatype in Clojure), you could always define:

    (defn cons?
      [x]
      (instance? clojure.lang.Cons x))

    (defn list-cons
      [v l]
      (cond
        (cons? l) (list-cons v (into '() (reverse l))) 
        (list? l) (conj l v)
        :else     (throw (AssertionError. "Assert failed: list-cons requires a PersistentList or a Cons."))))
    
If you really want a Lisp-style cons with pairs and such, you'll need to define that yourself. It's not hard, or particularly useful, but if you really feel you must...



Unfortunately they print as (1 2 3), even though they are different data structures. A naive user would think that if (1 2 3) is a list, then anything that prints like (1 2 3) is a list... printing it and rereading it -> I get a new data structure type. Really?

Added with the random use of Lisp names to mean something different, while claiming to be a Lisp, this is only creating confusion.


Cons prints the same as a list, which means when it is read, it is a list. '(1 2 3) is a PersistentList, not a Cons. In particular, this is what I expect:

    (= foo (read-string (pr foo)))
As it turns out, in Clojure this holds (because = isn't identical?):

    (use 'clojure.test)
    (let [kons (cons 1 '(2 3))
          kons-str (pr-str kons)
          read-kons (read-string kons-str)]
      (is (instance? clojure.lang.Cons kons))
      (is (not (list? kons)))
      (is (string? kons-str))
      (is (= "(1 2 3)" kons-str))
      (is (list? read-kons))
      (is (not (instance? clojure.lang.Cons read-kons)))
      (is (= kons read-kons)))
Does this really create confusion for more than the 30 seconds it takes to read the doc string of clojure's cons? I don't know about you, but when I move to a different language (even a different lisp), I don't assume things always work exactly like they do in Common Lisp. When I came to clojure, I even took a look at the old version of this page: https://clojure.org/reference/lisps, which details this and other differences from the Schemes I had mostly used in the past.


That may only create a few seconds of impedance, but it's still utterly psychotic and insane. Once again: if cons isn't cons, than don't call it cons. I don't assume that everything works the same way everwhere, but if you take your primitive function names from another language, and have them behave differently, people will be confused.

I mean, sure, if the semantics of cons are a bit different, okay, but you can't just take the name and put it on a function that doesn't work at all like cons.

It also violates homoiconicity, and the fundamental guarantee of the reader: what you read in is the same as what you wrote out.


> doesn't work at all like cons

clojure.lang.Cons follows what CL's cons does with exactly one deviation: cdr must implement clojure.lang.ISeq. The practical effect of this is that you don't get a Cons cell as a primitive building block, but you don't need that because you have deftype (or one of the many higher level interfaces in clojure, which tend to be faster anyway since deftype will end up using java fields for storage, which the JVM understands very well). I don't have to build up my own alist using a list of pairs containing keys and values because I just use a map. Further, when I'm using deftype, if I implement the right interfaces, the core clojure sequence functions like conj, first, rest, map, filter, reduce, etc will all work on my custom type, without me inventing a namespace worth of custom implementations for those.

> violates homoiconicity

Clojure code isn't written using Lists, Vectors, Strings, Maps, Keywords, and Symbols? I guess I'll stop writing macros then. No clue how they ever managed to work.

> fundamental guarantee of the reader

AFAICT, clojure's printer makes no such contractual guarantee here. FWIW, neither does CL (witness the thousands of subtly (and not so subtly) incompatible read tables floating around). In clojure, the printer, out of the box, prints values in such a way that they can be read back in in a way that is `clojure.core/=`. It's unreasonable for them to be `clojure.core/identical?` (which is essentially asking if they are the same memory as before (this isn't even something that Common Lisp or Scheme does, mostly because it's pretty insane unless you statically map out all of your memory)). I mention out of the box because you can change the printer and supply additions to the object tag reader (or even to throw out clojure's reader and use one of the reimplmentations as a library).


>clojure.lang.Cons follows what CL's cons does with exactly one deviation: cdr must implement clojure.lang.ISeq

Well then, it's not a cons, is it? It's actually a particularly weird implementation of push, which stores the pushed item in another cell. If you're not going to put cons in your language, fine, but be honest about it.

>Further, when I'm using deftype, if I implement the right interfaces, the core clojure sequence functions like conj, first, rest, map, filter, reduce, etc will all work on my custom type, without me inventing a namespace worth of custom implementations for those

So you have high-level collection functions that act the same between collections? Good for you guys. Lisp and scheme have had that for ages. But don't call your high-level functions by the name the low-level functions are expected to be called, and expose those low-level functions: sometimes a function wants to know what sort of structure it's manipulating.

>Clojure code isn't written using Lists, Vectors, Strings, Maps, Keywords, and Symbols? I guess I'll stop writing macros then. No clue how they ever managed to work.

Okay, yeah, I miswrote that, it doesn't violate homoiconicity. But I disagree vehemently about the reader. Assuming the readtable is in the same state, anything you write out can be read back in to give the same structure in RAM: that's why you can use write to serialize state.

Also, by the above, clojure violates commutative equality, which is bad in any language that claims to support FP: If a property is false on an entity, than an entity that entity is equal to cannot have that property be true. Otherwise, they're not equal.


before list? on a cons result is false, after reading it back it is something different and list? is no longer true?

Even though both print as (...)?

Okay...


It may be a lisp, but I'm not having that debate in two threads at once :-). Nonetheless, this is poor design, and poor language ergonomics in actions. I think Rainer and I can agree on that much.


What I was saying is that that is really unintuitive. Cons has a specific meaning and clojure redefines it creatively. That's not a good thing. If your function isn't cons than don't call it that.

Also, lisp pairs are very useful.




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

Search: