mettleap
mettleap

Reputation: 1410

Clojure: Update map inside a method

I have a use case where I want to update one of my map type variables inside a method call. To demonstrate here is a code snippet,

(defn func [mymap]
    (conj mymap [1 2])) ;update mymap variable here such that changes are persistent after the method returns

(let [mymap {}
    _ (func mymap)] (println mymap))

which outputs {} because I think a new map is created with the conj function. How do I update the mymap variable in func such that the output of the above program will be {1 2}?

If it is not possible in Clojure, how are such use cases handled in general?

Upvotes: 0

Views: 93

Answers (2)

cfrick
cfrick

Reputation: 37008

Clojure uses immutable data types by default. This means, you cannot mutate the data in place like you are used to from many other programming languages.

The functional approach here is to use the result from the conj (the last statement inside a defn is it's return value).

(let [mymap {} 
      result (func mymap)]
  (println result))

The longer you can keep up with pure functions on immutable data the easier your life will be; reasoning about your programs and testing them becomes a lot easier.

There is of course the option to use mutable data classes from Java, but don't use them unless you really have to.

And since nearly all programs need some state, there are also atom:s

  • I only mention this here, because short of def everywhere, atom everywhere are the next best "trap" beginners run into.

Upvotes: 1

Alan Thompson
Alan Thompson

Reputation: 29958

Many choices. Simplest is to rebind the mymap variable. Consider:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn doit [mymap]
      (into mymap {:b 42}))

(dotest
  (let [m {:a 1}
        m2 (doit m)]
    (spyx m2)))

m2 => {:a 1, :b 42}

and we get what we expect. Update the code to reuse the name m:

(dotest
  (let [m {:a 1}
        m (doit m)]
    (spyx m)))

m => {:a 1, :b 42}

Here the 2nd usage of m creates a separate variable that shadows the first m. This works great and people do it accidentally all the time without even realizing it.

If you want to copy the behavior of Java, you need a Clojure atom to create a mutable storage location.

(dotest
  (let [m-atom (atom {:a 1})]
    (swap! m-atom doit)
    (spyx @m-atom)))

(deref m-atom) => {:a 1, :b 42}

Here swap! applies the function doit to the contents of m-atom, the puts the results as the new contents.

We need the @m-atom or (deref m-atom) to pull out the contents of the atom for printing.

The above convenience functions can be found here. I also have some great documentation references in this template project. Be especially sure to study the Clojure Cheatsheet daily.

Upvotes: 1

Related Questions