Updating a value nested deeply within a data structure can be a little tricky in my experience.

Clojure, and functional programming in general, provides some really neat strategies for manipulating nested immutable data structures.

In this post, I'll try to use the real world example of adding radio buttons to a html form to give an overview of the basic functions and techniques clojure provides for updating nested data structures.

In Part 2, I'll try to build on what we come up with here and explore some more advance functions for manipulating nested data structures.

## Total Calories Burned per Day

In a previous blog post, I made a BMR calculator. Let's try to add radio buttons to the BMR calculator in order to calculate total calories burned per day. By doing so, we'll be forced to think about how to update values that are deeply nested inside the data structure that represents the calculator's state.

Your BMR only gives an estimate of the amount of calories your body burns when it's at rest. So, BMR is really only half the equation. In addition to BMR, we need to know how many calories our bodies burn during activities.

In order to get a complete estimate of the total number of calories your body burns each day (in order to maintain current weight), we can take the BMR from the last post, and multiply it as follows:

• Little to to no exercise, BMR * 1.2
• Light exercise (1-3 days per week), BMR * 1.375
• Moderate exercise (3-5 days per week), BMR * 1.55
• Heavy exercise (6-7 days per week), BMR * 1.725
• Very Heavy exercise (twice per day, extra heavy workouts), BMR * 1.9

(These multipliers are part of the Harris-Benedict equation)

## The Current Application State

The BMR Calculator uses a data structure that looks like this:

``````(def state (atom
{:gender "m"
:weight {:value 220 :unit "lbs"}
:height {:ft 6 :in 2}
:age 36}))
``````

You can see a live example of how the calculator updates this state here

At this point, this is a pretty simple hash map. We can use `swap!`, and `assoc` to easily replace the value of `220` with `180`. But don't worry! If you're not quite comfortable with this yet, don't stop reading, we're going to break it down even simpler.

``````(swap! state assoc-in [:weight :value] 180)
``````

Let's forget about the `atom` for a moment and start with a simple map named `state-map`:

``````user> (def state-map
{:gender "m"
:weight {:value 220 :unit "lbs"}
:height {:ft 6 :in 2}
:age 36})
``````

We can use assoc-in to transform this map into a new map with the value updated to 180.

``````user> (assoc-in state-map [:weight :value] 180)

{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36}
``````

The `assoc-in` function requires 3 arguments:

• The first is the map that you want to transform (in this case`state-map`).
• The second is a vector that represents the "path" to the value youwant to replace (in this case `[:weight :value]`).
• And the third is the value you'd like to change it to (in this case, `180`).

Still confused? Yeah, I was too when I first saw this stuff. Let's back up even more ...

### The `get` Function

First, remember that we can use the get method like this

``````user> (get {:value 220 :unit "lbs"} :value)
220
``````

### Keywords are like `get`

Also, remember that clojure keywords (the things that start with colons, `:`) can be used to look up themselves. (I know, crazy, right?) Sort of like a shorthand for `get` function. So we also could have also done this:

``````user> (:value {:value 220 :unit "lbs"})
220
``````

### The `assoc` Function

We can use the assoc method to replace (or insert) values like this:

``````user> (assoc {:value 220 :unit "lbs"} :value 180)
{:value 180, :unit "lbs"}
``````

### The `update`

And the update function is also very handy. It's almost exactly like `assoc`. Except, it takes a function instead of a value. The function you pass to `update` has to look like something like this:

``````(fn [old-value] ;; return new value here )
``````

So, we can use `update` like this:

``````user> (update {:value 220 :unit "lbs"}
:value
(fn [old] (println "Goodbye, " old) 180))
Goodbye,  220
{:value 180, :unit "lbs"}
``````

See how that works? The return value of `180` is used to replace the old value of `220`. (I used `println` just to show that the old value is available just in case you need it for anything). Pretty neat, eh?

Ok, so far, so good. Those are the basic functions. We can start with these basic functions and build on them. So, lets do it.

## It gets complicated quickly

We really want to work with the entire `state-map` instead of little pieces of it at a time. So, let's try to use `get` and `assoc` together in order to process the whole `state-map`:

``````user> (assoc state-map :weight              ;;--> #3
(assoc (get state-map :weight) ;;--> #1
:value 180              ;;--> #2
))

{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36}
``````

Wow, sort of confusing, right? We have to get the nested map associated with key `:weight` (#1), and then associate that with the `:value` of 180 (#2), and then `assoc` that whole result back to the key `:weight` (#3).

If you think that's a little painful, then you're in luck.

## `assoc-in` to the Rescue!

Now we've come full circle back to `assoc-in`. `assoc-in` is like `assoc` but, instead of passing a single key, we can pass a vector of keys. The vector of keys describes the path into the map. If you know css, you can think of it sort of like css selector paths. So, we can rewrite the last expression like this:

``````user> (assoc-in state-map [:weight :value] 180)

{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36}
``````

The `update-in` function corresponds to the `assoc-in` function just like `update` corresponds to the `assoc` function. Instead of a value, we need to pass a function (which takes the old value and return a new value.

``````user> (update-in state-map [:weight :value] (fn [_] 180)

{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36}
``````

(Note that since we don't really care about the old value in this case, we can just use `_` as the argument variable. That `_` symbol is just a nice way to show that even though an argument is required, we really don't care what it is)

Now that we've grokked `assoc-in` and `update-in`, we can finally get to some radio buttons.

I chose to represent the exercise levels as another `map` where the keys are the exercise levels (like `:sedentary`, `:light`, etc) and the values are maps which contain more keys that describe attributes of each level. So, now `state-map` looks like this:

``````(def state-map
{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36
:levels {:sedentary {:multiplier 1.2
:desc "Little or no exercise"
:checked true}
:light     {:multiplier 1.375
:desc "Light exercise (1-3 days/week)"
:checked false}
:moderate  {:multiplier 1.55
:desc "Moderate exercise (3-5 days/week)"
:checked false}
:active    {:multiplier 1.725
:desc "Hard exercise (6-7 days/week)"
:checked false}
:heavy     {:multiplier 1.9
:desc "Very hard exercise (2x training)"
:checked false}
}})
``````

When a user clicks on a radio button, we'll need to update the corresponding radio button so that `:checked` is `true`.

Using `assoc-in`, that's easy enough. For example, let's pretend a user clicked on the "light" radio button. Let's set the `:checked` key in the `:light` map to true.

``````user> (assoc-in state-map [:levels :light :checked] true)

(def state-map
{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36
:levels {:sedentary {:multiplier 1.2
:desc "Little or no exercise"
:checked true}
:light     {:multiplier 1.375
:desc "Light exercise (1-3 days/week)"
:checked true}
:moderate  {:multiplier 1.55
:desc "Moderate exercise (3-5 days/week)"
:checked false}
:active    {:multiplier 1.725
:desc "Hard exercise (6-7 days/week)"
:checked false}
:heavy     {:multiplier 1.9
:desc "Very hard exercise (2x training)"
:checked false}
}})
``````

Easy Peasy. But now see the problem? "sedentary" is still set to true as well. To fix that, we also need to make sure that `checked` is set to `false` for all the other radio buttons. This is really going to test our nested map updating skills.

## List Comprehensions on Maps

First off, we can processes key value pairs in a map as a list using the following pattern:

``````user> (for [[k v] (:levels state-map)] [k v])

([:sedentary
{:multiplier 1.2, :desc "Little or no exercise", :checked true}]
[:light
{:multiplier 1.375,
:desc "Light exercise (1-3 days/week)",
:checked false}]
[:moderate
{:multiplier 1.55,
:desc "Moderate exercise (3-5 days/week)",
:checked false}]
[:active
{:multiplier 1.725,
:desc "Hard exercise (6-7 days/week)",
:checked false}]
[:heavy
{:multiplier 1.9,
:desc "Very hard exercise (2x training)",
:checked false}])
``````

That's called a list comprehension (in functional speak). And this fits perfectly with what we need. By treating the map as a list, we can easily process each level and make sure the `:checked` key is "false". Let's give it a shot:

``````user> (into {}                           ;;=> #3
(for [[k v] (:levels state-map)] ;;=> #1
[k (assoc v :checked false)]   ;;=> #2
))

{:sedentary
{:multiplier 1.2, :desc "Little or no exercise", :checked false},
:light
{:multiplier 1.375,
:desc "Light exercise (1-3 days/week)",
:checked false},
:moderate
{:multiplier 1.55,
:desc "Moderate exercise (3-5 days/week)",
:checked false},
:active
{:multiplier 1.725,
:desc "Hard exercise (6-7 days/week)",
:checked false},
:heavy
{:multiplier 1.9,
:desc "Very hard exercise (2x training)",
:checked false}}
``````

Beautiful. We use the trick of using a `for` list comprehension to treat a map like a list of things (#1). Next, we replace `:checked` with `false` for each level in the list using our good friend `assoc` (#2). And finally, transform the list back to a map using `into {}`.

## All Together Now

Drum roll, please. Let's put all the pieces together to update the `:light` exercise level so that `:checked` is true, while ensuring that all the other exercise levels have `:checked` false:

``````user> (update-in state-map [:levels]
#(-> (into {} (for [[k v] %]
[k (assoc v :checked false)]))
(assoc-in [:light :checked] true)))

{:gender "m",
:weight {:value 180, :unit "lbs"},
:height {:ft 6, :in 2},
:age 36,
:levels
{:sedentary
{:multiplier 1.2, :desc "Little or no exercise", :checked false},
:light
{:multiplier 1.375,
:desc "Light exercise (1-3 days/week)",
:checked true},
:moderate
{:multiplier 1.55,
:desc "Moderate exercise (3-5 days/week)",
:checked false},
:active
{:multiplier 1.725,
:desc "Hard exercise (6-7 days/week)",
:checked false},
:heavy
{:multiplier 1.9,
:desc "Very hard exercise (2x training)",
:checked false}}}
``````

Voila!

## Next Up

If you think that was cool, in my next post, I'll keep banging on this same use case to describe some cool alternative functions that can do this same sort of thing.