Sorry if if it was not clear. I was not asking for more documentation. I understand that the documentation of `conj` should not say anything about how the different implementations work since it is only about adding an element to a collection in constant time. And you can always have future implementations of the operation that will behave differently. So the conj function has the right documentation in my opinion. It describes its semantic.
My question is about having implementations "extend" the semantic of conj. By extending the semantic I mean that it allows the user to have more expectations about the behavior of the operation. If we use conj on a vector, we expect the element to be added at the end. Our programs rely on this property. In this sense, we extend the semantic of conj in my opinion. With operations like prepend/append, the semantic is in a way complete. The different sorted collection implementations will always behave the same when prepending or appending an element (except for the time complexity of course :)). I'm trying to find other examples in Clojure and other programming languages where implementations of an abstraction extend the semantic of the abstraction. And to be completely clear, I'm not complaining about the design decision in Clojure. I'm just trying to really understand what the design is, what are the trade-offs and where the confusion might come from. On Thu, Jul 19, 2018 at 4:18 PM Andy Fingerhut <andy.finger...@gmail.com> wrote: > > > On Thu, Jul 19, 2018 at 1:04 PM Benoît Fleury <m...@benfle.com> wrote: > >> Replying to myself ... :) >> >> 1. Am I wrong in thinking that most Clojure programmers use append/prepend >> as mental models for the different implementations of conj? >> >> No. >> >> 2. Am I wrong in thinking that they shouldn't? >> >> Yes. >> >> Clojure developers need to know the behavior of conj for vectors and >> lists. This is even what often drives the choice between the two data >> structures. >> >> And I think that's where a lot of confusion comes from. I think I >> (and maybe others) expect that the complete semantic of a function >> be described in its documentation. I don't know whether this >> expectation is justified or not :) >> > > You are not alone in wishing for more details in documentation of Clojure > functions. That is one of the reasons ClojureDocs.org exists, for example, > and its first few examples make explicit the different behavior of conj for > lists vs. vectors: http://clojuredocs.org/clojure.core/conj > > If you wish for the Clojure core team to provide that style of > documentation for you, I expect your wish will not be fulfilled. It is not > technically difficult to replace all Clojure doc strings with more verbose > variants. It _is_ a lot of work to write documentation like that and > maintain it over time. For example, here is a whole bunch of stuff I wrote > on the behavior of clojure.core/= and clojure.core/hash. It took way more > hours than I care to admit: > https://github.com/jafingerhut/thalia/blob/master/doc/other-topics/equality.md > > I believe someone wrote a book aiming to be a reference to all Clojure > functions, but don't recall the name or author at the moment, nor have I > read it. > > Andy > > > >> >> >> On Thu, Jul 19, 2018 at 11:04 AM Benoît Fleury <m...@benfle.com> wrote: >> >>> I agree with Alex. It is important to understand the rationale behind the >>> behavior of conj. conj is for adding an element to a collection. It >>> doesn't >>> say anything about ordering. It has been chosen as an operation, as >>> opposed to append/prepend, because it can be implemented with the >>> same time complexity for a lot of data structures. >>> >>> To convince myself that time complexity mattered in programming, I take >>> the example of a stack. If you give me an implementation of a stack that >>> push and pop items in linear time, I will write very inefficient >>> programs >>> very quickly. To the point of not working at all. So it makes sense to >>> me >>> that space/time complexity should be part of the signature of an >>> operation. >>> >>> Now, I think the problem here comes from the fact that we have a tendency >>> to infer the semantic of an operation from its behavior on certain >>> types. We >>> see that (conj [1 2] 3) returns [1 2 3] so we conclude that conj is for >>> adding >>> an item at the end of a collection. And then we get surprised when the >>> behavior is "different" for other types. As Clojure programmers, it is >>> something >>> we need to be careful about and make sure we understand the actual >>> semantic of the operations. >>> >>> However, I think it is easier said than done. I would be ready to bet >>> that a >>> lot of Clojure programs rely on the fact that conj *appends* to a vector >>> or >>> *prepends* to a list. I agree that it is problematic. First, it makes the >>> understanding of the code harder because you need to remember the >>> behavior >>> of conj on each type. And it prevents the implementation of conj to >>> change >>> because so many programs rely on implementation details. But to all >>> expert >>> Clojure programmers here, can you honestly think about what mental model >>> you >>> use when using conj on vectors and lists? Are you really thinking: I'm >>> adding >>> this element to this collection. I don't really care where it ends up :) >>> ? >>> >>> So my questions are: >>> >>> 1. Am I wrong in thinking that most Clojure programmers use >>> append/prepend >>> as mental models for the different implementations of conj? >>> >>> 2. Am I wrong in thinking that they shouldn't? >>> >>> 3. And if I'm not wrong, how could we make it easier for programmers to >>> make >>> sure they code against the semantic of an operation and not its >>> implementation >>> details? Is there methods, tools or tests that could help us with that? >>> >>> >>> On Thu, Jul 19, 2018 at 3:20 AM Didier <didi...@gmail.com> wrote: >>> >>>> Hey Alan, >>>> >>>> Nice job on Tupelo by the way. I do find it a bit bloated though, and >>>> that's why I never use it. Any reason why all the different namespace are >>>> still mixed together? Seem like they could each be an independent lib, like >>>> how the datomic namespace was split out. >>>> >>>> On Wednesday, 18 July 2018 13:16:15 UTC-7, Alan Thompson wrote: >>>>> >>>>> There is also a function `glue` >>>>> <https://github.com/cloojure/tupelo#gluing-together-like-collections> >>>>> for combining like collections: >>>>> >>>>> >>>>> --------------------------------------------------------------------------------- >>>>> Gluing Together Like Collections >>>>> >>>>> The concat function can sometimes have rather surprising results: >>>>> >>>>> (concat {:a 1} {:b 2} {:c 3} );=> ( [:a 1] [:b 2] [:c 3] ) >>>>> >>>>> In this example, the user probably meant to merge the 3 maps into one. >>>>> Instead, the three maps were mysteriously converted into length-2 vectors, >>>>> which were then nested inside another sequence. >>>>> >>>>> The conj function can also surprise the user: >>>>> >>>>> (conj [1 2] [3 4] );=> [1 2 [3 4] ] >>>>> >>>>> Here the user probably wanted to get [1 2 3 4] back, but instead got >>>>> a nested vector by mistake. >>>>> >>>>> Instead of having to wonder if the items to be combined will be >>>>> merged, nested, or converted into another data type, we provide the >>>>> glue function to *always* combine like collections together into a >>>>> result collection of the same type: >>>>> >>>>> ; Glue together like collections: >>>>> (is (= (glue [ 1 2] '(3 4) [ 5 6] ) [ 1 2 3 4 5 6 ] )) ; all >>>>> sequential (vectors & lists) >>>>> (is (= (glue {:a 1} {:b 2} {:c 3} ) {:a 1 :c 3 :b 2} )) ; all maps >>>>> (is (= (glue #{1 2} #{3 4} #{6 5} ) #{ 1 2 6 5 3 4 } )) ; all sets >>>>> (is (= (glue "I" " like " \a " nap!" ) "I like a nap!" )) ; all >>>>> text (strings & chars) >>>>> ; If you want to convert to a sorted set or map, just put an empty one >>>>> first: >>>>> (is (= (glue (sorted-map) {:a 1} {:b 2} {:c 3}) {:a 1 :b 2 :c 3} )) >>>>> (is (= (glue (sorted-set) #{1 2} #{3 4} #{6 5}) #{ 1 2 3 4 5 6 } )) >>>>> >>>>> An Exception will be thrown if the collections to be 'glued' are not >>>>> all of the same type. The allowable input types are: >>>>> >>>>> - >>>>> >>>>> all sequential: any mix of lists & vectors (vector result) >>>>> - >>>>> >>>>> all maps (sorted or not) >>>>> - >>>>> >>>>> all sets (sorted or not) >>>>> - >>>>> >>>>> all text: any mix of strings & characters (string result) >>>>> >>>>> >>>>> >>>>> On Wed, Jul 18, 2018 at 1:13 PM, Alan Thompson <cloo...@gmail.com> >>>>> wrote: >>>>> >>>>>> As someone mentioned, the functions `prepend` and `append` exist in >>>>>> the Tupelo library >>>>>> <https://github.com/cloojure/tupelo#adding-values-to-the-beginning-or-end-of-a-sequence> >>>>>> to prevent this kind of confusion: >>>>>> >>>>>> from the README: >>>>>> >>>>>> ------------------------------------------------------------------------------------ >>>>>> 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 >>>>>> >>>>>> Do you know what conj does when you pass it nil instead of a >>>>>> sequence? It silently replaces it with an empty list: (conj nil 5) ⇒ >>>>>> (5) This can cause you to accumulate items in reverse order if you >>>>>> aren’t aware of the default behavior: >>>>>> >>>>>> (-> nil >>>>>> (conj 1) >>>>>> (conj 2) >>>>>> (conj 3));=> (3 2 1) >>>>>> >>>>>> 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 <code >>>>>> style="box-sizing:border-box;font-family:SFMono-Regular,Consolas,"Liberation >>>>>> >>>>> -- >>>> 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. >> > -- > 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.