I ran into a special case that I'd like to avoid. When (filter ...) is
either applied to an empty collection or the filter predicate returns
false for all the elements in the supplied collection, filter returns
nil.
Would it be acceptable and desirable for it to return and empty list in
this case?
Randall Schulz
To make the case concrete and possibly get a suggestion of a better way
to deal with the situation I encountered, here the function that ran
into the "nil return from (filter ...)" condition. It uses (flatten ...) from
clojure.contrib.seq-utils:
This was my original function. It return [nil] when no matching slots
are found:
(defn- gather-slot-values
"Extract all the values paired with the specified key;
returns a vector of flattened values"
[slot-key c-pairs]
(into [] (flatten (map #(second %) (filter #(= (first %) slot-key) c-pairs))))
)
Here's how I modified it so it returns [] in that case:
(defn- gather-slot-values
"Extract all the values paired with the specified key;
returns a vector of flattened values"
[slot-key c-pairs]
(let [filtered (filter #(= (first %) slot-key) c-pairs)]
(if filtered
(into [] (flatten (map #(second %) filtered)))
[]))
)
Recently, I was writing a function that yields a sequence of all the
subsequences of a given sequence, e.g.,
(subseqs '(1 2 3)) ->
(nil (1) (2) (3) (1 2) (1 3) (2 3) (1 2 3))
I had a very hard time deciding whether nil or '() belonged there
(eventually I decided on nil).
It turns out that flatten (from clojure.contrib.seq-utils) is really
where I go wrong with my simple version of gather-slot-values.
Does this makes sense (specifically the last one)?
user=> (flatten [])
nil
user=> (flatten ())
nil
user=> (flatten nil)
(nil)
Randall Schulz
I would expect (flatten nil) => nil
> ...
Randall Schulz
If my current experience is any indicator (I'll leave it to you to
evaluate the "if" part of that), there will be a proliferation of these
tests at the point of calling of flatten if it's not done that one time
within it.
> Here's flatten, as written by
> Rich:
>
> (defn flatten [x]
> (let [s? #(instance? clojure.lang.Sequential %)]
> (filter (complement s?) (tree-seq s? seq x))))
>
> Which I think can now be simplified to:
>
> (defn flatten2 [x] (filter (complement coll?) (tree-seq coll? seq
> x)))
>
> Should maps be flattened in addition to "sequential things"?
My intuition is "no," but that's a pretty shallow intuition at this
point.
> But still (flatten2 nil) => (nil)
> In general, you get weird results if the thing passed to flatten is
> not a vector or a list:
>
> (flatten "hello") => ("hello" \h \e \l \l \o)
> (flatten2 "hello") => ("hello" \h \e \l \l \o)
>
> -Stuart Sierra
Randall Schulz
Flattening nothing gives something?
Flatten is not consistent with seq:
user=> (flatten ())
nil
user=> (flatten [])
nil
user=> (flatten 0)
java.lang.IllegalArgumentException: Don't know how to create ISeq from: Integer
user=> (flatten nil)
(nil)
user=> (seq ())
nil
user=> (seq [])
nil
user=> (seq 0)
java.lang.IllegalArgumentException: Don't know how to create ISeq from: Integer
user=> (seq nil)
nil
> Not a bug. Implementation deficiency/wrong expectations/
> preconceptions/mis-application of earlier idiom to new platform.
Please. A little civility, OK?
Yes. I've read it.
> As I understand it, the `problem' is the interop with Java, ISeq, &
> null
This has nothing to do with Java interoperation.
> s_P
Randall Schulz
'(nil) is a list containing the single element nil. nil is no kind of
list whatsoever. So, (seq? '(nil)) is true, since '(nil) is a list and
lists are seqs and (seq? nil) is false since nil is not a sequence.
The quoted list equivalent of (seq? nil) is not (seq? '(nil)), it's
(seq? '()) and it rightly returns false:
1:1 user=> (seq? '())
false
This is why flatten's behavior was considered a bug. In Clojure, an
empty sequence is equivalent to nil, not to '(nil).
HTH,
- J.
It is also the case that empty lists are self-evaluating:
user=> ''()
(quote ())
user=> '()
()
user=> ()
()
>...
>
> - J.
Randall Schulz
Ahhh, good to know. Thanks!
- J.
I didn't say () was the same as nil, I said an empty sequence was and
that you should be comparing (seq? nil) to (seq? ()), not (seq?
'(nil)).
> and this one:
> "In Clojure nil means 'nothing'. It signifies the absence of a value,
> of any type, and is not specific to lists or sequences."
>
> and probably this one too:
> "Empty collections are distinct from nil. Clojure does not equate nil
> and '()."
These docs are all correct and consistent. Also, I did not equate nil
and '(), I equated (seq? nil) and (seq? '()), and only to illustrate
why (flatten nil) should not return '(nil).
> and this one as well:
> "...There is no such thing as an empty sequence. Either there is a
> sequence (with elements) or there isn't (nil)..."
This is exactly the point. As Randall pointed out, flattening nothing
should not create something (since '(nil)) is something. Remember, you
don't expect a list out of (flatten), you expect a sequence and an
empty sequence is nil.
> add this to the pile:
> "...The Clojure return values differ in not returning empty
> collections, but rather a sequence or not...."
Again, this is exactly why flatten should return nil in this case. It
should return "a sequence or not" and flattening nothing should give
you nothing.
I don't see any inconsistencies here.
- J.