Quote:
Originally Posted by fragment
Isn't the idea to avoid loop constructs if possible? The usual reason for an incrementing loop variable is to use as an index to refer to elements of some array/vector/whatever but in a functional language you can use map, filter or reduce instead.
|
Absolutely right. But after many years of procedural programming, I tend to reach for loop constructs to tackle pretty much everything, and it's frustrating when they're not available.
For example, my sudoku program has deeply nested recursive routines to solve a puzzle. The top level routine loads puzzles to solve from a .sdm file which is a standard sudoku community ASCII file that contains one puzzle on each line of the file. The one I'm using to test my program has over a thousand puzzles.
So I think, "I'll use a loop that loads one puzzle at a time, then pass that puzzle to the solver, and once the solver has finished, move on to the next puzzle." I also want to print each puzzle to the screen, along with its line-number in the .sdm file. How to do that without an incrementing loop variable? It's possible, of course - and once you begin to think in the Clojure way it begins to look elegant.
Clojure has the idea of 'lazy sequences' that produce lists of things, but each item in the list is only generated when it's needed. You can have things that all look the same, but have different types. For example here I define a, b, and c all of which look the same when printed in the REPL (interactive Clojure console) but have different types.
Code:
=> (def a (range 0 10))
a
=> a
(0 1 2 3 4 5 6 7 8 9)
=> (type a)
clojure.lang.LongRange
=> (def b (take 10 (range)))
b
=> b
(0 1 2 3 4 5 6 7 8 9)
=> (type b)
clojure.lang.LazySeq
=> (def c (into () (range 9 -1 -1)))
c
=> c
(0 1 2 3 4 5 6 7 8 9)
=> (type c)
clojure.lang.PersistentList
You can pass those three different types (and plenty of others) to the same map operation, and the output is identical. Here I'm mapping an anonymous function that multiplies by two to each of a, b, c
Code:
=> (map #(* % 2) a)
(0 2 4 6 8 10 12 14 16 18)
=> (map #(* % 2) b)
(0 2 4 6 8 10 12 14 16 18)
=> (map #(* % 2) c)
(0 2 4 6 8 10 12 14 16 18)
In each case the type of the output is a clojure.lang.LazySeq because map is prepared to accept pretty much any kind of collection but always returns a lazy sequence. I think map kind-of uses seq internally because seq returns a sequence on any collection.
But even the operation of seq confuses me. (seq a), (seq b), and (seq c) all produce output that looks just like a, b, c alone. (seq a) has the same type as a, and (seq c) has the same type as c, but (seq b) has type clojure.lang.Cons
I get very confused, but I suppose that's an important stage of any learning process.