Clojure Notes--labrepl 2

I came across this problem while trying to write the step function (labrepl, cellular-automata, Let 'Er Rip, step 6). I needed to execute the function brians-brain-rules on each of the 3 x 3 sequences in a variable I called ee:

solutions.automaton=> ee
([(:8 :5 :6) (:4 :1 :2) (:8 :5 :6)]
[(:5 :6 :7) (:1 :2 :3) (:5 :6 :7)]
[(:6 :7 :8) (:2 :3 :4) (:6 :7 :8)]
[(:7 :8 :5) (:3 :4 :1) (:7 :8 :5)])

First, I discovered how to execute brians-brain-rules on one such element

solutions.automaton=> (def eee (first ee))
#'solutions.automaton/eee
solutions.automaton=> eee
[(:8 :5 :6) (:4 :1 :2) (:8 :5 :6)]

solutions.automaton=> (brians-brain-rules eee) ; DOES NOT WORK
#<CompilerException java.lang.IllegalArgumentException: Wrong number of args (1) passed to: automaton$brians-brain-rules (NO_SOURCE_FILE:149)>

solutions.automaton=> (apply brians-brain-rules eee) ; DOES WORK
:off

Next, I try to work on the entire data structure. I felt that I had to map the combination of apply and brians-brain-rules to ee, but it took several tries to get it right:

solutions.automaton=> (apply brians-brain-rules ee)
#<CompilerException java.lang.IllegalArgumentException: Wrong number of args (4) passed to: automaton$brians-brain-rules (NO_SOURCE_FILE:151)>
solutions.automaton=> (doc comp)


clojure.core/comp
([f] [f g] [f g h] [f1 f2 f3 & fs])
Takes a set of functions and returns a fn that is the composition
of those fns. The returned fn takes a variable number of args,
applies the rightmost of fns to the args, the next
fn (right-to-left) to the result, etc.
nil
solutions.automaton=> (map (apply brians-brain-rules) ee)
#<CompilerException java.lang.IllegalArgumentException: Wrong number of args (1) passed to: core$apply (NO_SOURCE_FILE:154)>
solutions.automaton=> (map (comp apply brians-brain-rules) ee)
#<IllegalArgumentException java.lang.IllegalArgumentException: Wrong number of args (1) passed to: automaton$brians-brain-rules>
solutions.automaton=> (doc partial)


clojure.core/partial
([f arg1] [f arg1 arg2] [f arg1 arg2 arg3] [f arg1 arg2 arg3 & more])
Takes a function f and fewer than the normal arguments to f, and
returns a fn that takes a variable number of additional args. When
called, the returned function calls f with args + additional args.
nil

partial is what worked:

solutions.automaton=> (map (partial apply brians-brain-rules) ee)
(:off :off :off :off)
solutions.automaton=>

For me, this highlighted the difference between partial and comp (compose): ((partial f g) x) is like (f g x), which is what I wanted to do the equivalent of (apply brians-brain-rules eee); ((comp f g) x) is equivalent to (f (g x)), which doesn't make sense in this situation.


9/26/10

Finally, I've successfully finished the cellular-automata project! The part I spent the most time on was the function with the deceptively of step. (step board) calculates the next value of all the cells in board according to the rules of the Brian's Brain algorithm (similar to John Conway's The Game of Life, but with cells that can be on, dying, or off).

(defn step2 ;my solution, without let...
  "Advance the automation by one step, updating all cells."
  [board]
  (map (partial map (partial apply brians-brain-rules))
    (map (partial apply map vector) (torus-window (map torus-window board)))))

(defn step ;labrepl's solution
  "Advance the automation by one step, updating all cells."
  [board]
  (doall
   (map (fn [window]
          (apply #(apply map brians-brain-rules %&)
                 (doall (map torus-window window))))
        (torus-window board))))

Given that the labrepl tutorial indicated that the only non-core-Clojure functions needed were brians-brain-rules and torus-window, I figured that any minimalistic, elegant solution that I came up with would be more-or-less equivalent to labrepl's. Boy, was I wrong! And what really blows my mind is that by the standards of the Clojure community (which seems to value terse, functional solutions), mine is significantly shorter than the one given by labrepl (5 lines for step2 vs. 8 lines for the labrepl solution, or 7 if you want to merge map into the next line)!

Working with a very simple board (b, below), I made individual transformations that got me closer and closer to the desired result—four steps, represented by the calculations c, d, e, and f (also below). I put those in the function step2 (below, at the end). I created the function step (above) from step2 by chaining all the calculations together and removing the let bindings.

(def b [[:1 :2 :3 :4] [:5 :6 :7 :8]])
(def c (map torus-window b))
(def d (torus-window c))
(def e (map (partial apply map vector) d))
(def f (map (partial map (partial apply brians-brain-rules)) e))

(defn step ;my solution
  "Advance the automation by one step, updating all cells."
  [board]
  (let [augment-rows (map torus-window board)
        augment-cols (torus-window augment-rows)
        matrices-gathered (map (partial apply map vector) augment-cols)]
    (map (partial map (partial apply brians-brain-rules)) matrices-gathered)))

If you look at the code below, with some study you can see how my version works. Using a beginning board b with the elements :1 through :8 makes it easier to track the transformations.

solutions.automaton=> b
[[:1 :2 :3 :4] [:5 :6 :7 :8]]

The first transformation augments each row with the value from the other end (e.g., [:4 :1 :2 :3 :4 :1]), then chops each row into overlapping triplets, as specified by torus-window:

solutions.automaton=> c
(((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
 ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5)))

The second transformation, doing the same thing in the vertical (column) direction, augments the original two-column matrix by prepending the last element (row 2) and appending the first element (row 1), then chopping it up into overlapping subsequences of three elements each (where an element here represents an augmented row). See the result below. If you look at it and squint, you will see a 4 x 2 matrix of 3 x 3 squares. Each of these 3 x 3 squares represents the original element in b and its surrounding eight neighbors (assuming wraparound in both the horizontal and vertical directions). Each such matrix is the exact matrix needed to compute that position's next value.

solutions.automaton=> d
((((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
  ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
  ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5)))
 (((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
  ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
  ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))))

The next transformation pulls the front 3-tuple off each row to create the 3 x 3 matrix for each element of the original 2 x 4 matrix, b.

solutions.automaton=> e
(([(:8 :5 :6) (:4 :1 :2) (:8 :5 :6)] ;for b, row 1, column 1
  [(:5 :6 :7) (:1 :2 :3) (:5 :6 :7)] ;for b, row 1, column 2
  [(:6 :7 :8) (:2 :3 :4) (:6 :7 :8)] ;for b, row 1, column 3
  [(:7 :8 :5) (:3 :4 :1) (:7 :8 :5)]) ;for b, row 1, column 4
 ([(:4 :1 :2) (:8 :5 :6) (:4 :1 :2)] ;for b, row 2, column 1
  [(:1 :2 :3) (:5 :6 :7) (:1 :2 :3)] ;for b, row 2, column 2
  [(:2 :3 :4) (:6 :7 :8) (:2 :3 :4)] ;for b, row 2, column 3
  [(:3 :4 :1) (:7 :8 :5) (:3 :4 :1)])) ;for b, row 2, column 4

The final transformation simply applies the transforming function, brians-brain-rules, to each 3 x 3 matrix, thus giving the new value for the board b. (Since the original values in b don't make sense to the function -- it's expecting the values :on, :dying, and :off -- all the values in the final result happened to be :off.

solutions.automaton=> f
((:off :off :off :off) (:off :off :off :off))

The transformed data structures were (relatively) easy to envision, but the Clojure code needed to transform each step into the next were real brainbusters!

This leads me to a new insight about Clojure: even though you write much less code than you would in Java, you will spend the same -- if not more -- time getting the code written. The advantage, of course, is that the functional code thus written is far more likely to be correct than its (imperative) Java equivalent!

And (at the moment) I have absolutely no idea how the labrepl version of step works -- sigh.


9/30/10

Figuring out labrepl's step function

This is the code we are trying to understand:

01  (defn active-neighbors
02    "Count the active (:on) neighbors one cell away from me in
03     any direction. Maximum of 8."
04    [[[nw n ne]
05      [w  _ e ]
06      [sw s se]]]
07    (count
08     (filter
09      #{:on}
10      (concat [nw n ne w e sw s se]))))

1  (defn brians-brain-rules
2    "Determine the cell's next state based on its current
3     state and number of active neighbors."
4    [above [_ cell _ :as row] below]
5    (cond
6     (= :on    cell)                              :dying
7     (= :dying cell)                              :off 
8     (= 2 (active-neighbors [above row below]))   :on  
9     :else                                        :off  ))

1  (defn torus-window
2    "The torus window is a cursor over the board, with each item
3     containining a cell and all its immediate neighbors."
4    [coll]
5    (partition 3 1 (concat [(last coll)] coll [(first coll)])))

1  (defn step
2    "Advance the automation by one step, updating all cells."
3    [board]
4    (doall
5     (map (fn [window]
6            (apply #(apply map brians-brain-rules %&)
7                   (doall (map torus-window window))))
8          (torus-window board))))

This is the board we are going to be using:

solutions.automaton=> (def b [[:1 :2 :3 :4] [:5 :6 :7 :8]])

solutions.automaton=> b
[[:1 :2 :3 :4] [:5 :6 :7 :8]]

c is the value calculated in step, line 8

solutions.automaton=> (def c (torus-window b))

solutions.automaton=> c
(([:5 :6 :7 :8] [:1 :2 :3 :4] [:5 :6 :7 :8])
 ([:1 :2 :3 :4] [:5 :6 :7 :8] [:1 :2 :3 :4]))

If I substitute the value of c into the step function, maybe I'll have a better chance of understanding what the code is doing:

1  (defn step2 ;version 2
2    "Advance the automation by one step, updating all cells."
3    [board]
4    (doall
5     (map (fn [window]
6            (apply #(apply map brians-brain-rules %&)
7                   (doall (map torus-window window))))
8          (([:5 :6 :7 :8] [:1 :2 :3 :4] [:5 :6 :7 :8])
9                ([:1 :2 :3 :4] [:5 :6 :7 :8] [:1 :2 :3 :4])))))

Let's define the unnamed function as fn, so we can further simplify the code we have to look at:

1  (defn fn [window]
2         (apply #(apply map brians-brain-rules %&)
3                (doall (map torus-window window))))

Now, substituting fn for the anonymous function:

1  (defn step3 ;version 3
2    "Advance the automation by one step, updating all cells."
3    [board]
4    (doall
5     (map fn
6           (([:5 :6 :7 :8] [:1 :2 :3 :4] [:5 :6 :7 :8])
7            ([:1 :2 :3 :4] [:5 :6 :7 :8] [:1 :2 :3 :4])))))

From step3, lines 5-7, we can further reduce the amount of code we're studying by saying that we will understand this code if we understand how (fn (first c)) works:

solutions.automaton=> (def cc (first c))

solutions.automaton=> cc
([:5 :6 :7 :8] [:1 :2 :3 :4] [:5 :6 :7 :8])

Suppose we define cc2 as (last c). Then lines 5-7 of function step3, above, has the form (map fn (cc cc2)), which will be evaluated as {{(fn cc) (. Since cc and cc2 have the same structure, we can assume that they will look the same when evaluated. Therefore, it'll be sufficient for me to understand what

Now let's substitute cc into the body of fn:

1   ; code fragment 1
2   (apply #(apply map brians-brain-rules %&)
3          (doall (map torus-window ([:5 :6 :7 :8] [:1 :2 :3 :4] [:5 :6 :7 :8]))))

Now let's figure out what line 2 immediately above means:

solutions.automaton=> (def dd (map torus-window cc))

solutions.automaton=> dd
(((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
 ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
 ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5)))

Substituting the value of dd into code fragment 1, we get the following:

1   ; code fragment 2
2   (apply #(apply map brians-brain-rules %&)
3          (((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
4           ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
5           ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5)))))

Let's execute the equivalent of code fragment 2 to see what happens:

solutions.automaton=> (apply #(apply map brians-brain-rules %&) dd)
(:off :off :off :off)

This confirms that our code manipulations so far are still valid.

Can we substitute the value of dd for the symbol? Will that work? First, let's get the type of dd:

solutions.automaton=> (type dd)
clojure.lang.LazySeq

Now let's get the type of the printed representation of dd (note that we quote its value to prevent the REPL from trying to execute it:

solutions.automaton=> (type '(((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
 ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
 ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))))
clojure.lang.PersistentList

Not too promising, but let's try it anyway, executing code fragment 2 with the argument of lines 3-5 being quoted:

solutions.automaton=> (apply #(apply map brians-brain-rules %&) '(((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
 ((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
 ((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))))
(:off :off :off :off)

Seems to work! I have a working rule-of-thumb that says that (apply xxx ( …stuff… ) is equivalent to (xxx …stuff… ). This will mean quoting all the forms that are suddenly exposed. Trying it results in the following:

solutions.automaton=> (#(apply map brians-brain-rules %&) '((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
 '((:4 :1 :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
 '((:8 :5 :6) (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))))
(:off :off :off :off)

Yikes! We're still on course! Using the "apply" rule-of-thumb again and quoting the data that I don't want to be evaluated, I then execute:

solutions.automaton=> (map brians-brain-rules 
     '((:8 :5 :6)  (:5 :6 :7) (:6 :7 :8) (:7 :8 :5))
     '((:4 :on :2) (:1 :2 :3) (:2 :3 :4) (:3 :4 :1))
     '((:8 :5 :6)  (:5 :6 :7) (:6 :7 :8) (:7 :8 :5)))
(:dying :off :off :off)

OMG! It works! Or at least it seems to. To confirm this, I'll change the input to reflect some symbols that brians-brain-rules should transform in a known fashion. Look at the input below, which has been adjusted so that you can more easily see the 3 x 3 cells being operated on. I've changed the value in the center of each 3 x 3, which is the value that will change based on its value and the value of all the cells surrounding it. I've also changed the values of some of the surrounding cells. According to the rules of brians-brain-rules, :on always transitions to :dying, :dying always transitions to :off, :off with exactly two :on cells surrounding it always transition to :on, and :off in all other situations remains :off. These four cases are represented by (from left to right) the data below.

solutions.automaton=> (map brians-brain-rules
     '((:8 :5 :6)  (:5 :6 :7)        (:on :7 :8)  (:on :8 :on))
     '((:4 :on :2) (:1 :dying :3)    (:2 :off :4) (:3 :off :1))
     '((:8 :5 :6)  (:5 :6 :7)        (:on :7 :8)  (:on :8 :5)))
(:dying :off :on :off)

As you can see, the results are as we expected.

So a final mystery remains: How does the code above translate three sequences, each containing four items of three subitems each, into four sequences, each containing three items of three subitems each? You can see that this is what happens when we substitute list (which is essentially a null operator in this situation) for brians-brain-rules:

solutions.automaton=> (map list
     '((:8 :5 :6)  (:5 :6 :7)     (:on :7 :8)  (:on :8 :on))
     '((:4 :on :2) (:1 :dying :3) (:2 :off :4) (:3 :off :1))
     '((:8 :5 :6)  (:5 :6 :7)     (:on :7 :8)  (:on :8 :5)))
(((:8  :5 :6)  (:4 :on :2)    (:8 :5 :6))
 ((:5  :6 :7)  (:1 :dying :3) (:5 :6 :7))
 ((:on :7 :8)  (:2 :off :4)   (:on :7 :8))
 ((:on :8 :on) (:3 :off :1)   (:on :8 :5)))

Hear's a reformatting of the result above that makes its structure more obvious:

(((:8 :5  :6)
  (:4 :on :2)
  (:8 :5  :6))  ((:5 :6     :7)
                 (:1 :dying :3)
                 (:5 :6     :7))  ((:on :7   :8) 
                                   (:2  :off :4)
                                   (:on :7   :8))  ((:on :8   :on)
                                                    (:3  :off :1)
                                                    (:on :8   :5)))

At first, I was puzzled by this behavior. I thought map was used with single values, changing (map inc '(1 2 3 4)) into (2 3 4 5), for example. Then I read the online summary for map, which gave me a truly painful RTFM moment:

clojure.core/map

([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])

Returns a lazy sequence consisting of the result of applying f to the
set of first items of each coll, followed by applying f to the set
of second items in each coll, [and so on, —gw]
until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments.

I now realize that, in an example like (map inc '(1 2 3 4)), f is a function of one argument, and therefore it should be followed by one sequence. What part of Well, duh! don't I understand?

Live and (hopefully) learn.

(In case you're wondering about what the two occurrences of (doall (map … do, the explanation begins with the fact that map always returns a lazy sequence, which is not evaluated unless it is needed. The function of doall is simply to force that evaluation. I modified step so as to remove both occurrences of doall, and it still worked, so I don't know whether they are, strictly speaking, necessary.



10/3/10

Context: Working on exercises in the "defstrict" page of labrepl.

I was pretty clueless about the subject matter of this page and had to simply go with labrepl's code for the last half of the exercises on this page. When I was done, I couldn't even get the results are expected from exercising some of these functions. Here are some things I learned along the way:

Lesson 1: Always quote the macro form being expanded by macroexpand and macroexpand-1. (Includes free rant!)

This should work, right? (defstrict is a macro, by the way.)

solutions.defstrict=> (macroexpand
                        (defstrict shout [^String s]
                           (.toUpperCase s)))
#'solutions.defstrict/shout

But it doesn't. It turns out that you must quote the form you want to macroexpand, like this:

solutions.defstrict=> (macroexpand
                        '(defstrict shout [^String s]
                           (.toUpperCase s)))
(def shout (.withMeta (clojure.core/fn shout (# # #)) (.meta #'shout)))

I don't know about you, but I got absolutely no clue of this from the online docs:

solutions.defstrict=> (doc macroexpand-1)
-------------------------
clojure.core/macroexpand-1
([form])
  If form represents a macro form, returns its expansion,
  else returns form.

I guess this falls under the category of "remember that every s-expression within a form is evaluated before anything else happens." I'm guessing that without the quote, (defstrict shout [^String s] (.toUpperCase s)) is evaluated, thus defining shout (which would explain why the return "value" is #'solutions.defstrict/shout).

There's also the issue of knowing how to read the online docs. Looking at the definition above, the beginner would be justified in believing that the single argument to macroexpand-1 would be a vector containing the form - after all, it says that the argument is ([form]). Many of the online docs look very similar - for example:

solutions.defstrict=> (doc inc)
-------------------------
clojure.core/inc
([x])
  Returns a number one greater than num.

But that is not the case. This becomes obvious when you get the online documentation for + (or, for all that matter, many other built-in functions):

solutions.defstrict=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0.

To help the average outmanned, outgunned Clojure beginner, I offer the following verbal translation of the first two lines of the definition above: The function + ,which belongs to the namespace clojure.core, can take a variety of arguments, as shown on line 2. Reading from left to right, they are: no arguments, represented as "[]"; one argument, represented as "[x]"; two arguments, represented as "[x y]"; and three or more arguments, represented as "[x y & more]".

<rant>

It's unfortunate that nobody spells this out anywhere in the "official" Clojure documentation. Can the average programmer infer this from the existing documentation? Yes, of course. But why should they have to? A beginner is wrestling with all sorts of unfamiliar stuff - why put barriers in the way of understanding a programming language? It is beginner-unfriendly to do so, and the minimal attention lent to Clojure documentation is impeding Clojure adoption. The implicit message in such cryptic documentation is, "If you can't figure out what I'm saying, that's not my problem" - to which many programmers will say, "Why are people so crazy about this language? I don't have the time to struggle with this - I'm going back to <insert language name here>."

</rant>


10/4/10

Lesson 2: When using meta, remember to encode the symbol in question by preceding it with #' - for example, (meta #'symbol). This is the same as (meta (var symbol)) and even (meta #' symbol) (note the space between the #' and symbol.

solutions.defstrict=> (meta #'shout)
{:ns #<Namespace solutions.defstrict>,
 :name shout,
 :file "solutions\\defstrict.clj",
 :line 44,
 :arglists ([^String s])}

solutions.defstrict=> ((meta #'shout) :file)
"solutions\\defstrict.clj"

#' is what is called a reader macro, a macro that executes when the form is read by the system - in other words, it's a shorthand for something else. This particular reader macro transforms an input like #'foo or even #' foo (note this space in the middle) into (var foo). This usage is appropriate even if symbol is, say, a named function, because in such a case, its value is the function, and it has been bound to the symbol. (Binding a value to a symbol is as close as Clojure gets to what conventional imperative languages do, which is to assign a value to a variable.)

solutions.defstrict=> (doc meta)
-------------------------
clojure.core/meta
([obj])
  Returns the metadata of obj, returns nil if there is no metadata.

After trying many, many things to find what argument that instance-check expects, I finally found what works:

solutions.defstrict=> (instance-check '^String s)
(clojure.core/instance? String ^String s)

This led me to discovering the way of getting the meta information on a type-hinted symbol:

solutions.defstrict=> (meta '^String s)
{:tag String}

The rule of thumb here is the argument is simply the symbol, quoted. What's unexpected is that the string "^String s", which has a space character in the middle, seems to be regarded as the valid representation of a symbol. A symbol containing a space character is valid? Does this bother anybody as much as it bothers me?

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License