Brilliant exposition, thanks! On Fri, Jul 20, 2018, 10:51 AM Gary Johnson <lambdatro...@gmail.com> wrote:
> Hi Christian, > > You are looking for "into", which is already part of the Clojure standard > library. > > Appending: > > (into '(1 2) '(3)) ;=> (1 2 3) > (into [1 2] [3]) ;=> [1 2 3] > (into {:a 1 :b 2} {:c 3}) ;=> {:a 1, :b 2, :c 3} > (into #{:a :b} #{:c}) ;=> #{:c :b :a} > > Prepending: > > (into '(1) '(2 3)) ;=> (1 2 3) > (into [1] [2 3]) ;=> [1 2 3] > (into {:a 1} {:b 2 :c 3}) ;=> {:a 1, :b 2, :c 3} > (into #{:a} #{:b :c}) ;=> #{:c :b :a} > > The "into" function pours the contents of the second collection into the > first collection, returning a collection of the same type as the first > argument. > > That being said, I agree with Alex and James in this rather lengthy > discussion. Clojure is unique among the Lisps in that it moved beyond > having linked lists as the only first class data structure. > > Prior to Clojure, if you worked in a Lisp like Scheme or Common Lisp, you > would design your program around the creation, traversal, and manipulation > of linked lists using higher order functions and explicit recursions. The > standard library in both languages is heavily focused on these list-related > operations. After developing the initial version of your program, if you > found that it was too slow or used too much memory, the accepted practice > was to profile your application to identify the functions that were getting > hammered the most and were using up the majority of your computing > resources. You would then often end up rewriting those function bodies in > an imperative fashion using loops and mutable data structures (i.e., arrays > and hashtables). The "wisdom" here was that this would enable you to "first > make it right, then make it fast". If even further performance was required > from your program, you might then rewrite part of your program in C, build > a foreign function interface (FFI) to link the C code into your Lisp > program, and go from there. These were the Bad Old Days of Lisp(TM). > > What was IMHO quite possibly Rich's greatest contribution in the design of > Clojure to the Lisp world was his decision to make additional data > structures first class citizens of the language. Most importantly, he did > so by creating Clojure's vectors, maps, and sets to be immutable, > persistent, performant, recursively constructed, and representable as data > literals. This was already a wonderful improvement over previous Lisps, but > it created a new problem: How could we enjoy the pure delight of > list-oriented programming that Lisp had always offered us now that the data > structure space had been fragmented? A famous quote from Alan Perlis is a > popular gem in the Lisp world, and it goes like so: > > "It is better to have 100 functions operate on one data structure than to > have 10 functions operate on 10 data structures." > > Every Lisp had always satisfied this by simply giving programmers only one > first class data structure to use: the linked list. As I already mentioned, > the bulk of its standard library would then be built around list > manipulation functions. Clojure needed a way to preserve this unified style > of programming while still providing a collection of performant data > structures for real-world programming. So how did Rich accomplish this? > > He created the "sequence abstraction". A sequence in Clojure serves a > similar role to the linked list of previous Lisps in that it unifies the > API for interacting with all of Clojure's first class data structures > (list, vector, map, set). By calling the function "seq" on any data > structure, you are given a list-like view of that collection that allows > you to traverse it from beginning to end one element at a time and to add > new elements to the beginning of it. These operations are called "first", > "rest", and "cons", and they behave precisely as you would expect them to > if you were calling them on a linked list. > > By using seq throughout the Clojure sequence library (i.e., the set of > standard library functions responsible for creating, traversing, > transforming, and manipulating sequences), Clojure is able to have single > implementations of all of the common Lispy higher order list transformation > functions. For example, we have "map", "filter", "reduce", "iterate", > "take", "drop", "repeat", "cycle", and so on. The amazing thing is that > these can all take any of Clojure's data structures as their inputs. So you > can call map on a list, vector, map, or set without having to change the > function signature. Without the sequence abstraction, we could need > multiple functions for every data structure we wanted to support (e.g., > map-list, map-vec, map-hash, map-set, filter-list, filter-vec, filter-hash, > filter-set). This is precisely the kind of combinatorial explosion of the > function space the Alan Perlis was warning us about. The tradeoff is that > each of these higher order functions will then return a new sequence as its > output. While this prints to the REPL like a list, please note that a > sequence is not a list (except when it is a sequence on a list ;-D ). It is > a list-like representation of the contents of any data structure. You can > check this by calling the "type" function on the output of either "seq" or > any higher order function (e.g., map, filter, reduce) that calls seq > internally. > > So when you are programming Clojure or teaching it to new programmers (as > I have done on numerous occasions), it really is important IMHO to take a > moment to appreciate the history that motivated Rich's design decisions > around data structures and the sequence abstraction and not to simply write > it off and treat Clojure as though it were Scheme or Common Lisp made to > run on the JVM. > > In Clojure, the choice of your data structures is central in the design of > your programs when it comes to performance. However, an equally important > part of program design is the conceptualization of much of your program as > a series of sequence transformations composed together so as to reach the > output you desire from the inputs you are given. To that end, if you wish > to equip new programmers with the skills to think like a Clojure > programmer, I would first teach them the four main data structures (list, > vector, map, set) and the functions to operate on each of them. Next, I > would teach them the sequence API and demonstrate how these four data > structures are represented as sequences. This enable everyone to reason in > a straightforward manner about all of the sequence functions going forward. > Then, I would teach them how to use higher order functions like map, > filter, reduce, and range to replace loops and mutation in their program > logic. After this, I would discuss recursion and function composition as > the fundamental components of flow control in a functional programming > language. Finally, I would spend some time going over dynamic vs lexical > scoping rules, shadowed bindings, namespaces, and the call stack. > > This should provide your students with most of the groundwork that they > need to get going with Clojure programming and to dig deeper into various > advanced topics like host interop, concurrency primitives, parallel > programming, spec, pure/impure functions, macros, and so on. > > One thing that I would definitely avoid in teaching a new language is to > alter the syntax of that language on day 1 and teach constructs that are > neither efficient nor particularly useful in practice. To that end, I would > advise you to use the "into" function that I demonstrated at the beginning > of this email if you wish to teach a unified API for appending and > prepending to each of Clojure's four main data structures while preserving > the types of the function's inputs. > > And with that, I'm going to head back to my day job. Good luck in learning > Clojure and teaching it to others, and don't hesitate to reach out with > questions to the Clojure mailing list. Most of the folks on here are > usually very friendly and intelligent, and I've always found that to be a > hallmark of this community. > > Happy hacking, > Gary > > -- > 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.