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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.