1
votes

In Ruby, there is Kernel#Array method, which acts like this:

Array([1, 2, 3]) #=> [1, 2, 3]
Array(1..5)      #=> [1, 2, 3, 4, 5]
Array(9000)      #=> [9000]
Array(nil)       #=> []

In other words, it converts nil to empty array, non-collections to singleton arrays, and kinds of collection-like objects (i.e. objects which respond to #to_ary or #to_a) to arrays.

I want to have something similar to this in Clojure:

(to-seq [1 2 3])     ;=> (1 2 3)
(to-seq (range 1 5)) ;=> (1 2 3 4)
(to-seq 9000)        ;=> (9000)
(to-seq nil)         ;=> nil; () is ok too

That's what I've got so far:

(defn to-seq [x] (if (seqable? x) (seq x) '(x)))

But I don't like it because:

  1. The seqable? function evaporated during the explosion of monolithic clojure-contrib. I don't want to include huge ancient no-longer-supported library to my project just for one function.
  2. I have a feeling that there must be a built-in function that is either in clojure.core or in clojure.contrib.whatever. Or that there's more idiomatic approach.

The exact output type of to-seq is not important. The main thing is that I'd like to use its output, say, in list comprehensions:

(for [person (to-seq person-or-people)] ...)

So if will be a vector - it's ok. If it will be a vector, a Java array, a list or nil depending on the input - that's fine too.

==== UPD ====

The almost final version:

(defn to-seq [x] (if (or (nil? x) (coll? x)) x [x]))

The final version:

(defn to-seq ..blabla..)

I don't need to-seq, it doesn't look attractive.

2
As a side note, '(x) evaluates to a singleton list containing the symbol x; you'll want to use (list x) or [x] instead.Michał Marczyk

2 Answers

3
votes

If you only need to iterate over the input and it is actually a container of some sort, you don't usually need to perform any special conversions. The two special cases which come to mind are handled by enumeration-seq and iterator-seq.

That being said, I believe there actually is no ready-made function in core for determining whether an object is a valid input to seq. The relevant code resides in clojure.lang.RT's seq and seqFrom methods and basically tries several possibilities in turn; I believe seqable? checked the same possibilities -- you could do the same. Existing functions like coll?, seq?, sequential? all fail to catch at least some of the cases (notably arrays and strings); coll? probably comes closest, sequential? is notably used by flatten (which is therefore limited in its ability actually to flatten things).

Of course there's always the possibility of trying to apply seq and catching the exception (not that I'd recommend it):

(defn seq-at-all-costs [x]
  (try
    (seq x)
    (catch IllegalArgumentException e
      (list x))))   ; NB. '(x) wouldn't work, since it would
                    ; cause a literal symbol x to be returned,
                    ; as noted in my comment on the question

You could also do something like this to speed things up (hopefully -- I'd definitely benchmark):

(defprotocol IKnowHowToSeqYou
  (seq-knowledgeably [this]))

(defn seq! [x]
  (try
    (seq-knowledgeably x)
    (catch IllegalArgumentException e
      (try
        (seq x)
        (extend-type (class x)
          IKnowHowToSeqYou
          (seq-knowledgeably [this]
            (seq this)))
        (catch IllegalArgumentException e
          (extend-type (class x)
            IKnowHowToSeqYou
            (seq-knowledgeably [this]
              (list this)))))
      (seq-knowledgeably x))))

I have to say, though, that I hardly ever -- perhaps actually never -- feel the need for seqable?. The reason is probably that it's relatively unusual that I have completely no idea what types of objects I'll be dealing with at any given point, and in those rare cases I tend to go through cases / use protocols / use multimethods etc.


Here's the checklist used by RT.seq and RT.seqFrom as of now:

  1. if input is already a seq (which to RT.seq means "an instance of clojure.lang.ASeq", not ISeq), it is returned unchanged;

  2. if it's a lazy seq, it is forced and the result is returned;

  3. otherwise if it implements Seqable, then it's asked to produce a seq of its contents;

  4. if it happens to be nil (null), nil is returned;

  5. if it's Iterable, an iterator seq is returned;

  6. if it is an array, an array seq is returned;

  7. if it is a CharSequence, a string seq is returned;

  8. if it is a java.util.Map, a seq over its entry set is returned;

  9. otherwise an exception is thrown.

2
votes
(map coll? [() [] #{} {} 1 "sandwich" :a nil])) 
;=> (true true true true false false false false)

(defn to-seq [x] (if (coll? x) (seq x) (list x)))

coll? any use?

See also: seq? or sequential?