Emtpy (filter ...) Result Yields nil; Could / Should It Be An Empty List?

32 views
Skip to first unread message

Randall R Schulz

unread,
Dec 14, 2008, 2:08:20 PM12/14/08
to clo...@googlegroups.com
Hi,

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

Randall R Schulz

unread,
Dec 14, 2008, 2:28:18 PM12/14/08
to clo...@googlegroups.com

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)))
[]))
)

Rich Hickey

unread,
Dec 14, 2008, 2:34:39 PM12/14/08
to Clojure
No.

The seq protocol says either there is a sequence of at least one
thing, or no sequence - nil. There is no such thing as an empty seq,
and no reason sequence functions should start returning empty
collections of any type.

Rich

Mark Engelberg

unread,
Dec 14, 2008, 2:42:46 PM12/14/08
to clo...@googlegroups.com
When IS an appropriate time to use '()? It's in the language, so I
assume there must be at least one good use case.

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).

Randall R Schulz

unread,
Dec 14, 2008, 3:16:47 PM12/14/08
to clo...@googlegroups.com
On Sunday 14 December 2008 11:08, Randall R Schulz wrote:
> ...

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

Michel Salim

unread,
Dec 14, 2008, 9:16:16 PM12/14/08
to Clojure


On Dec 14, 3:16 pm, Randall R Schulz <rsch...@sonic.net> wrote:

> Does this makes sense (specifically the last one)?
>
> user=> (flatten [])
> nil
>
> user=> (flatten ())
> nil
>
Yes; the empty sequence is just nil.

> user=> (flatten nil)
> (nil)
>
That does look like a bug.

--
Michel

Mon Key

unread,
Dec 15, 2008, 12:55:51 AM12/15/08
to Clojure
> user=> (flatten nil)
> (nil)

Not a bug.

flatten returns a sequence - in this case a sequence containing 'nil.
How else would you flatten on nil?

In the former cases the flatten returns the seq - nil
Can Clojure return an empty sequence otherwise?

(def x ())
(first x)
=> nil
(def y x)
y
=> nil

user> (def y x)
#'user/y
user> y
nil
user> (flatten y)
(nil)

Mon Key

unread,
Dec 15, 2008, 12:56:21 AM12/15/08
to Clojure
> user=> (flatten nil)
> (nil)

Not a bug.

flatten returns a sequence - in this case a sequence containing 'nil.
How else would you flatten on nil?

In the former cases the flatten returns the seq - nil
Can Clojure return an empty sequence otherwise?

(def x ())
(first x)
=> nil
(def y x)
y
=> nil

user> (def y x)
#'user/y
user> y
nil
user> (flatten y)
(nil)


On Dec 14, 9:16 pm, Michel Salim <michel.syl...@gmail.com> wrote:

Randall R Schulz

unread,
Dec 15, 2008, 9:08:33 AM12/15/08
to clo...@googlegroups.com
On Sunday 14 December 2008 21:56, Mon Key wrote:
> > user=> (flatten nil)
> > (nil)
>
> Not a bug.
>
> flatten returns a sequence - in this case a sequence containing
> 'nil. How else would you flatten on nil?

I would expect (flatten nil) => nil


> ...


Randall Schulz

Stuart Sierra

unread,
Dec 15, 2008, 10:22:01 AM12/15/08
to Clojure
On Dec 15, 9:08 am, Randall R Schulz <rsch...@sonic.net> wrote:
> > flatten returns a sequence  - in this case a sequence containing
> > 'nil. How else would you flatten on nil?
>
> I would expect (flatten nil) => nil

I agree, it's a bug, but I'm not sure how to fix it, unless it's just
a special case that needs an "if". 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"?

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 R Schulz

unread,
Dec 15, 2008, 10:28:26 AM12/15/08
to clo...@googlegroups.com
On Monday 15 December 2008 07:22, Stuart Sierra wrote:
> On Dec 15, 9:08 am, Randall R Schulz <rsch...@sonic.net> wrote:
> > > flatten returns a sequence  - in this case a sequence containing
> > > 'nil. How else would you flatten on nil?
> >
> > I would expect (flatten nil) => nil
>
> I agree, it's a bug, but I'm not sure how to fix it, unless it's just
> a special case that needs an "if".

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

Mon Key

unread,
Dec 15, 2008, 12:30:14 PM12/15/08
to Clojure
> > > I would expect (flatten nil) => nil

Why?
=> nil
nil is not a sequence - your expectation is that Clojure flatten and
return `nothing'... which *would* be a bug

Not a bug. Implementation deficiency/wrong expectations/
preconceptions/mis-application of earlier idiom to new platform.

@ http://clojure.org/lisps

As I understand it, the `problem' is the interop with Java, ISeq, &
null

s_P

Randall R Schulz

unread,
Dec 15, 2008, 1:12:31 PM12/15/08
to clo...@googlegroups.com
On Monday 15 December 2008 09:30, Mon Key wrote:
> > > > I would expect (flatten nil) => nil
>
> Why?
> => nil
> nil is not a sequence - your expectation is that Clojure flatten and
> return `nothing'... which *would* be a bug

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?


> @ http://clojure.org/lisps

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

Stuart Sierra

unread,
Dec 15, 2008, 1:17:36 PM12/15/08
to Clojure
On Dec 15, 12:30 pm, Mon Key <s...@derbycityprints.com> wrote:
> > > > I would expect (flatten nil) => nil
>
> Why?

Sorry, Mon Key, but I have to agree with Randall here. All the
sequence-related functions (first, rest, filter, map, etc.) return nil
for nil. If flatten is returning a sequence, I expect it to do the
same, especially because:

(seq []) => nil
(flatten []) => nil
(seq nil) => nil

In the case of flatten, I think this is rarely going to make any
difference, but I'll patch it.

-Stuart Sierra

Lennart Staflin

unread,
Dec 15, 2008, 2:45:10 PM12/15/08
to Clojure
How about adding a rest around tree-seq:

(defn flatten
"Takes any nested combination of sequential things (lists, vectors,
etc.) and returns their contents as a single, flat sequence."
[x]
(let [s? #(instance? clojure.lang.Sequential %)]
(filter (complement s?) (rest (tree-seq s? seq x)))))


Mon Key

unread,
Dec 15, 2008, 11:56:31 PM12/15/08
to Clojure
> Flattening nothing gives something?

"Nothing" was minding the gap until you put it inside flatten's
sequence
interface at which point you got a flattened sequence full of nil.
Prior to
that there was *not* an empty list waiting for nil. Returning nothing
when in fact you
expect a list (even one full of nil) is equally contradictory

> Please. A little civility, OK?
I feel your pain. I apologize if you read my post otherwise. As you
suggest - flatten's behavior is not entirely clear. Pointing out the
docs was not meant as an act of crassness. They are however the only
source of codified design rationale.

> Sorry, Mon Key, but I have to agree with Randall here.
Is it not ok to have free will around here? :)

While it may be reasonable to expect (flatten nil) to return nil, as
written and as documented in the working context - flatten doesn't
appear to this mon_key as a Clojure bug.

> This has nothing to do with Java interoperation.

NOTHING? :=)

Ok - now that Mr. Sierra has weighed in and resolved the problem... I
have a question?

Aside from the obvious, and apropos my understanding that the former
unbuggy
flatten behavior has since been decreed to be buggy, what are the
mon_keys missing here?

user> (def x '(1 2 3))
=>#'user/x
user> x
=>(1 2 3)
user> (instance? clojure.lang.Sequential x)
=>true
user> (instance? clojure.lang.Sequential nil)
=>false
user> (instance? clojure.lang.Sequential '(nil))
=>true
user> (instance? clojure.lang.Sequential 'nil)
=>false
user> x
=>(1 2 3)
user> (seq? x)
=>true
user> (seq? nil)
=>false

s_P

Mon Key

unread,
Dec 16, 2008, 12:26:16 AM12/16/08
to Clojure
whoops, chopped of the end of that last message - forgot the nasake-no
ichigeki
user> (seq? '(nil))
==|]======>true


J. McConnell

unread,
Dec 16, 2008, 11:03:50 AM12/16/08
to clo...@googlegroups.com

'(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.

Randall R Schulz

unread,
Dec 16, 2008, 11:23:23 AM12/16/08
to clo...@googlegroups.com
On Tuesday 16 December 2008 08:03, J. McConnell wrote:
> ...

>
> '(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

It is also the case that empty lists are self-evaluating:

user=> ''()
(quote ())

user=> '()
()

user=> ()
()


>...
>
> - J.


Randall Schulz

J. McConnell

unread,
Dec 16, 2008, 11:28:06 AM12/16/08
to clo...@googlegroups.com
On Tue, Dec 16, 2008 at 11:23 AM, Randall R Schulz <rsc...@sonic.net> wrote:
>
> It is also the case that empty lists are self-evaluating:
>
> user=> ''()
> (quote ())
>
> user=> '()
> ()
>
> user=> ()
> ()

Ahhh, good to know. Thanks!

- J.

Mon Key

unread,
Dec 16, 2008, 11:59:06 AM12/16/08
to Clojure
> This is why flatten's behavior was considered a bug. In Clojure, an
> empty sequence is equivalent to nil, not to '(nil).

This does not comport with the various differences enumerated @
http://clojure.org/lisps
Perhaps they need to be changed. RH care to weigh in on this?

which says:
"() is not the same as 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 '()."

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)..."

add this to the pile:
"...The Clojure return values differ in not returning empty
collections, but rather a sequence or not...."


On Dec 16, 11:28 am, "J. McConnell" <jdo...@gmail.com> wrote:

Stuart Sierra

unread,
Dec 16, 2008, 12:18:05 PM12/16/08
to Clojure
Thanks, Lennart. That works, although it took me a few minutes to
figure out why. (For the record, tree-seq returns a copy of its
argument as the first item in the sequence.) This also means that

(flatten "string")
;=> (\s \t \r \i \n \g)

I've put in a patch.
-Stuart Sierra

On Dec 15, 2:45 pm, Lennart Staflin <LStaf...@gmail.com> wrote:

J. McConnell

unread,
Dec 16, 2008, 1:28:26 PM12/16/08
to clo...@googlegroups.com
On Tue, Dec 16, 2008 at 11:59 AM, Mon Key <st...@derbycityprints.com> wrote:
>
>> This is why flatten's behavior was considered a bug. In Clojure, an
>> empty sequence is equivalent to nil, not to '(nil).
>
> This does not comport with the various differences enumerated @
> http://clojure.org/lisps
> Perhaps they need to be changed. RH care to weigh in on this?
>
> which says:
> "() is not the same as nil"

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.

Reply all
Reply to author
Forward
0 new messages