Now that everyone's had a day to mull this puzzle over, here are my thoughts.

One way to think about this, is to think about what the statement of
purpose / contract of a generalized powerset function would be.  In
general, most functions on seq-able objects return sequences.  For
example, (rest [1 2 3]) yields (2 3). So we'd want to make powerset
consistent with Clojure's general behavior.  Also, like most Clojure
core functions, we'd expect sequences in the output to be lazy.  I
think there are two basic lines of reasoning:

1.  The most straightforward generalization is:
The powerset of a seq-able S is the (lazy) sequence of all (lazy)
subsequences of (seq S).
Thinking in this vein, you might figure that
(powerset [1 2 3]) should be (nil (1) (2) (3) (1 2) (1 3) (2 3) (1 2 3)).
One way to rationalize it is that we really need to have an empty
sequence in the sequence of answers, but there is no such thing as an
empty sequence, so we must use nil.
But the obvious flaw here is that this output violates our contract.
We said we'd generate a sequence of sequences, but (seq? nil) is
false.  nil is not the empty sequence, nor is it a sequence at all, so
we haven't accurately captured the function's intention with this
output.

2.  So one way to try to "fix" this problem is to put a concrete empty
object in the output sequence.  Since sequences are inspired by lists,
perhaps it makes sense to replace the nil with the empty list ().  So,
(powerset [1 2 3]) yields (() (1) (2) (3) (1 2) (1 3) (2 3) (1 2 3)).
But this has problems as well.  First, even though the result prints
as a list of lists, it is important to remember that these things are
not lists, but sequences derived from vectors (since the input is a
vector in our example).  For example, (cons 1 [2 3]) prints as (1 2
3), but (list? (cons 1 [2 3])) is false.
So it's difficult to even imagine what the contract would be for such
a function.  Perhaps, "The powerset of a seq-able S is the (lazy)
sequence of the empty list and all the (lazy) subsequences of (seq
S)." would be one way to word the statement of purpose.  But if the
input is a vector, it seems really odd to see an empty list in the
output.  Why a list?

So I think that the answer is that there is no good answer.  We really
need an empty sequence here, and Clojure doesn't have such a notion.

Clojure's choice to not have an empty sequence doesn't really cause
any inconvenience when dealing with flat sequences, but I have found
that it results in these sorts of dilemmas almost any time you deal
with sequences of sequences.  In discussions about generating a
sequence of all permutations, all combinations, etc., you inevitably
end up with arguments over issues like what (permutations nil) should
be (some say (nil), some say (()), and some say nil).  If you have an
empty sequence, these issues go away.

So ultimately, you just have to make a choice between nil and () and
go with it.  It all comes down to a gut feeling about which one is a
better stand-in for the notion of an empty sequence, even though
neither one truly is the empty sequence.  In practice, it doesn't seem
to matter too much which choice you make.  nil and () are almost
entirely interchangeable.
(cons 1 nil) is the actual concrete list (1), just as (cons 1 '()) is.
(conj nil 1) is the same as (conj '() 1)
(first nil) and (first '()) both yield nil.
(rest nil) and (rest '()) both yield nil.
(empty? nil) and (empty? '()) both yield true
(seq nil) and (seq '()) both yield nil.

There are certainly a few differences, of course.  nil and '() respond
differently to nil?, list? and not, for example.  But I haven't found
these sorts of tests to be common in code operating on nested
sequences.

Even though it doesn't make a huge difference, the lack of a clearcut
correct choice means that people will make different choices in
different codebases, which could prove problematic.

I have seen Rich's talk in which he explains that he chose to not have
an empty sequence because which empty sequence should it be?  Empty
list?  Empty vector?  Why should one empty object be privileged?  But
this reasoning never resonated with me.  Sequences and lists have a
special connection.  Sequences are intended to be a list-like
interface.  So any sequence-producing function produces either a
concrete list, or something that looks, feels, and tastes like a list.
 I've already gotten used to seeing (rest [1 2 3]) producing (2 3) in
the REPL (even though technically it's producing something list-like
and not actually producing a list).  So to me, it would be perfectly
natural to see (rest [1]) producing ().  () could reasonably serve as
both the empty list and the empty sequence since lists and sequences
are so closely related.

I assume that a design decision as fundamental as the choice to have
no empty sequence is probably set in stone at this point, so I think
it's safe to assume that this dilemma is here to stay.  At the moment,
I intend to advocate the use of nil over () as the closest surrogate
for an empty sequence.  Clojure is designed to not give one empty
collection privileged status, so putting () inside a sequence of
sequences feels more wrong to me.  Furthermore, including nil is often
more practical to code.  None of Clojure's built-ins actually return
(), so it's only feasible to have () represent the empty sequence if
it's the result of a separately-coded base case.  For many algorithms
where the sequence is produced by mapping a function over a bunch of
values, or by 'rest'ing a sequence until reaching empty, the algorithm
will most naturally include nil in the sequence.  So for consistency,
it's probably better to always choose nil over () in sequences of
sequences.

Anyone want to try to convince me otherwise?  If you think my logic is
flawed, I'd love to hear your take on this problem.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to