1

Say I have a board like this

(def board {:a 10 :b 12})

And a list of functions like this

(def funcs [(assoc board :a 2)
            (assoc board :b 4)
            (assoc board :a (inc (get board :a)))])

How would I go about applying each operation in the list to my board, having it update each time in a functional fashion.

Evaluating funcs in my repl, gives a list of return values after calling each function, but the board itself remains unchanged each time

user=> funcs
[{:a 2, :b 12} {:a 10, :b 4} {:a 11, :b 12}

Ideally I'd like the value of the board to update each time a function is ran

so after running the all commands the final state of the board should be: {:a 3 :b 4}

I'm aware this is possible using tail recursion but I'd like to avoid doing so as I'm almost certain a combination of reduce/apply/map will do the trick

1
  • If you want to mutate board, have a look at clojure atoms. Otherwise board should probably be an initial value and the new board (funcs) should be passed around in function parameters when you need it. Commented Sep 11, 2016 at 14:42

2 Answers 2

9

One of the defining traits of clojure is that its data structures are immutable. That means that board will never change, functions that operate on data structures return modified copies of the data structure. So what you've done in funcs is make a vector of three different boards, the original board with a fuction applied.

Usually what you want is to apply a bunch of functions to some initial value, where each function takes the returned value of the function before, then use the returned value for something. Usually passing it around in function parameters.

;; First of all, there's a function for that
(assoc board :a (inc (get board :a)))

;; The function update takes a map and a key and a function
;; It applies the function to value currently at key,
;; then sets key in the retuned "copy" of the map to be the return value of the function.

;; Equivalent to the above
(update board :a inc)

If you want an updated board with these functions applied, you need to to pass the return value through the functions, because the original board never changes, they all just return updated copies of their input.

(def updated-board
  ;; The innermost forms are evaluated first.
  (update (assoc (assoc board :a 2) :b 4) :a inc))

This can be made more readable by using the -> or "thread first" macro. It takes an initial value and forms which are missing the first argument, then rewrites the code to "thread" the return value of each one as the first argument to the next.

(def updated-board-threaded
  (-> board
      (assoc :a 2)
      (assoc :b 4)
      (update :a inc)))

;; You can expand the macro to see for yourself
;; that the two examples are exactly equivalent.
(macroexpand-1 '(-> board
                    (assoc :a 2)
                    (assoc :b 4)
                    (update :a inc)))

;;=> (update (assoc (assoc board :a 2) :b 4) :a inc)

This is the way to "think in clojure", functions usually just take immutable values and return other immutable values.

But sometimes you need something mutable, and clojure delivers this in the form of atoms. An atom can be thought of as a mutable box that contains an immutable value.

It uses the functions swap! and reset! to apply controlled mutation. And the function deref to get the current value.

(def board (atom {:a 10, :b 12}))

;; I'll define a function that takes a board and returns an updated version of it.
(defn do-stuff-with-board [b]
  (-> b
      (assoc :a 2)
      (assoc :b 4)
      (update :a inc)))

;; Get the current value of board.
(deref board) ;;=> {:a 10, :b 12}

;; Swap takes an atom and a function and
;; sets the value of the atom to be the return value of the function
(swap! board do-stuff-with-board)

;; Now the mutable board atom contains a new immutable value.
(deref board) ;;=> {:a 3, :b 4}

;; derefing an atom is a very usual operation, so there's syntax sugar for it
;; Equivalent to (deref board)
@board ;;=> {:a 3, :b 4}

reset! sets the value of board to be another value, like = in "normal" languages. It's not usually idiomatic to do this as it kinda says to the reader that the new value of the atom has nothing to do with the old one, but clojure is pragmatic, and sometimes it's what you need.

(reset! board "And now for something completely different.")

;; The value is now a string.
@board ;;=> "And now for something completely different."

As an aside. The data structures are not actually deep copies of each other, there is magic behind the scenes to make it almost as efficient as updating the data structure in place, but from the perspective of the programmer they are equivalent to deep copies in other languages.

Sign up to request clarification or add additional context in comments.

2 Comments

Jack Evans, madstap is right. However, "board" suggests you have a two-dimensional data structure in mind. If you really need the efficiency of a mutable 2-D data structure, you could consider using core.matrix. However, using the mutable versions of core.matrix matrices requires great care, because Clojure isn't designed for willy-nilly mutability, and it can bite you. (Learned that the hard way ....) A better strategy would probably be to use assoc in the way that madstap suggests.
@Mars If there are long chains of modifications, you could consider using transients. You'd have to write your own update!.
2

I'd like to suggest a different approach to @madstap's fine answer.

In ...

(def funcs [(assoc board :a 2)
            (assoc board :b 4)
            (assoc board :a (inc (get board :a)))])

... the elements such as (assoc board :b 4) are not functions: they are function calls, which, as @madstap points out, fail to modify whatever board refers to.

We can, without too much trouble, turn them into proper functions:

(def funcs [(fn [board] (assoc board :a 2))
            (fn [board] (assoc board :b 4))
            (fn [board] (assoc board :a (inc (get board :a))))])

The boards here are locals. Any distinct identifier would do as well:

(def funcs [(fn [b] (assoc b :a 2))
            (fn [b] (assoc b :b 4))
            (fn [b] (assoc b :a (inc (get b :a))))])

We can write a function to compose them:

(defn compose [fs]
  (fn [x] (reduce (fn [a f] (f a)) x fs)))

This is a simplified version of the standard comp. It applies the functions first to last instead of last to first. Now, for example, if

(def board {:a 10 :b 12})

... then

((compose funcs) board)
;{:a 3, :b 4}

Furthermore, we can modify compose to show the chain of results:

(defn compositions [fs]
  (fn [x] (reductions (fn [a f] (f a)) x fs)))

((compositions funcs) board)
;({:a 10, :b 12} {:a 2, :b 12} {:a 2, :b 4} {:a 3, :b 4})

Notice that compose and compositions are completely generic - they just do stuff with functions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.