Hi Colin,

I too have been bitten by this type of inconsistency in clojure.core
functions. The root of the problem is that conj has different behavior for
lists and vectors, and that a seq behaves like a list. When map, filter,
etc convert the source vector into a seq, the behavior of conj changes
accordingly.

In order to avoid this kind of unpredictability, you may wish to explore
some of the functions to the Tupelo library. The goal is to make things
simpler, more obvious & predictable, and as bulletproof as possible. One
example is the append function.  Here is a sample program comparing conj
and append:

(ns clj.core
  (:require [tupelo.core :as t] ))
(t/refer-tupelo)

(def v [1 2 3])

(conj v 4)                                  => [1 2 3 4]
(conj (map identity v) 4)                   => (4 1 2 3)
(conj (remove (constantly false) v) 4)      => (4 1 2 3)
(conj (filter identity v) 4)                => (4 1 2 3)

(t/append v 4)                              => [1 2 3 4]
(t/append (map identity v) 4)               => [1 2 3 4]
(t/append (remove (constantly false) v) 4)  => [1 2 3 4]
(t/append (filter identity v) 4)            => [1 2 3 4]


I think simpler and more bulletproof functions can go a long toward making
Clojure easier to use, especially for beginners or when you are uncertain
about the exact type of a parameter.

I've pasted the relevant part of the README.txt below.  Enjoy!

Alan

---------------------------------------------------------------------------------------------------------------------------------------------
(from
https://github.com/cloojure/tupelo#adding-values-to-the-beginning-or-end-of-a-sequence
)

Adding Values to the Beginning or End of a Sequence

Clojure has the cons, conj, and concat functions, but it is not obvious how
they should be used to add a new value to the beginning of a vector or list:

; Add to the end
> (concat [1 2] 3)    ;=> IllegalArgumentException
> (cons   [1 2] 3)    ;=> IllegalArgumentException
> (conj   [1 2] 3)    ;=> [1 2 3]
> (conj   [1 2] 3 4)  ;=> [1 2 3 4]
> (conj  '(1 2) 3)    ;=> (3 1 2)       ; oops
> (conj  '(1 2) 3 4)  ;=> (4 3 1 2)     ; oops
; Add to the beginning
> (conj     1  [2 3] ) ;=> ClassCastException
> (concat   1  [2 3] ) ;=> IllegalArgumentException
> (cons     1  [2 3] ) ;=> (1 2 3)
> (cons   1 2  [3 4] ) ;=> ArityException
> (cons     1 '(2 3) ) ;=> (1 2 3)
> (cons   1 2 '(3 4) ) ;=> ArityException

These failures are irritating and unproductive, and the error messages
don’t make it obvious what went wrong. Instead, use the simple prepend and
append functions to add new elements to the beginning or end of a sequence,
respectively:

  (append [1 2] 3  )   ;=> [1 2 3  ]
  (append [1 2] 3 4)   ;=> [1 2 3 4]

  (prepend   3 [2 1])  ;=> [  3 2 1]
  (prepend 4 3 [2 1])  ;=> [4 3 2 1]

Both prepend and append always return a vector result.









On Fri, Sep 9, 2016 at 5:36 AM, Stuart Sierra <the.stuart.sie...@gmail.com>
wrote:

> Functions like map/filter/remove are not "collection functions" but rather
> "sequence functions." The collection functions like conj preserve the type
> of their argument. Sequence functions, by contrast, coerce any argument to
> a sequence, and always return a sequence.
>
> Since Clojure 1.7, transducers provide a convenient way to compose
> sequence-like operations but produce a non-sequence as a result:
>
> user=> (into [] (comp (map inc) (filter even?)) [1 2 3 4 5])
> [2 4 6]
>
> mapv and filterv were convenience functions added before the introduction
> of transducers, as I recall.
> –S
>
>
>
>
> On Friday, September 9, 2016 at 6:23:37 AM UTC-4, Colin Yates wrote:
>>
>> Hi all,
>>
>> So in the spirit of exposing my ignorance to the internet :-), I have
>> just been bitten by a bug due to the behaviour of the core libraries which
>> I find really surprising:
>>
>> (def v [1 2 3])
>> (conj v 4) => [1 2 3 4]
>> (conj (map identity v) 4) => (4 1 2 3)
>> (conj (remove (constantly false) v) 4) => (4 1 2 3)
>> (conj (filter identity v) 4) => (4 1 2 3)
>>
>> In other words, I was relying on map, remove and filter preserving the
>> semantics (other than laziness) of the structure of the input, give it a
>> vector and you get a vector-like lazy sequence. This turns out not to be
>> the case.
>>
>> Now, I know there is mapv which returns a vector but why isn't there a
>> removev and a filterv etc.?
>>
>> What makes it more onerous for me is the fact conj states that its
>> behaviour differs depending on the concrete type, which is great, but how
>> am I supposed to know which concrete type is returned from
>> map|filter|remove? My assumption was it would be semantically equivalent to
>> the input (i.e. a vector in this case).
>>
>> The reason I have dodged this is because I don't frequently rely on
>> vector semantics but I am surprised this isn't better documented?
>>
>> Is it me?
>>
>> Thanks,
>>
>> Colin
>>
> --
> 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
> Note that posts from new members are moderated - please be patient with
> your first post.
> 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
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to clojure+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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
Note that posts from new members are moderated - please be patient with your 
first post.
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
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to