1

I'm learning Clojure at the moment, and I'm having difficulty getting into the functional mindset.

I have a map that I am trying to populate from the command line. This is the code I have so far:

(ns dungeonworld.core
  (:gen-class))

(def pc {:Name ""
         :Class ""
         :Race ""
         :Look ""
         :Str 0
         :Dex 0
         :Con 0
         :Wis 0
         :Int 0
         :Cha 0
         })

(defn getVals 
  []
  (println "Enter Name: ")
  (assoc pc :Name (read-line))
  (println "Enter Class: ")
  (assoc pc :Race (read-line))
  (println "Enter Look: ")
  (assoc pc :Look (read-line)))

(defn -main
  "Create a Dungeon World character map"
  [& args]
  (getVals))

However it only updates the last entry (:Look).

Questions: How do I achieve what I want to achieve in a more functional, Clojure-y way, and how come it only updates the last map element? Is a map the right type to use?

Many thanks in advance!

3 Answers 3

3

assoc returns a new map containing the new mapping. You are not assigning these new maps to anything so they are being discarded. You need to keep track of the intermediate maps - one way to repeatedly update a state from a sequence of inputs is to use reduce:

(defn prompt [m [msg key]]
  (println msg)
  (assoc m key (read-line)))

(defn getVals 
  []
  (reduce prompt pc [["Enter Name: ", :Name], ["Enter Class: ", :Race], ["Enter Look: ", :Look]]))
Sign up to request clarification or add additional context in comments.

1 Comment

I've been trying to thoroughly understand this code. It's the reduce function that calls prompt multiple times, correct? If I need a persistent variable, I can swap! it into a new atom, I suppose?
1

Your getVals function is creating three new maps and returning only the last one. One of the most important part of thinking functionally vs imperatively is to create new versions of values instead of modifying them in place.

Another way is to create a new map with the three new items and merge it into the starting map to get your desired map:

(defn getVals
  []
  (merge pc {:Name (do (println "Enter Name: ")
                       (read-line))
             :Race (do (println "Enter Class: ")
                       (read-line))
             :Look (do (println "Enter Look: ")
                       (read-line))}))

The reduce mentioned in another answer would work too but it may an unnecessary extra concept for you at this moment. I think It's better to learn about reduce from simpler examples.

You could also use an atom but that is mutable state but it's encouraged to keep that to an absolute minimum in Clojure and functional programming in general. This case doesn't seem to require it.

Comments

1

To add to the previous answers, you don't have to use mutable state in Clojure to have something you can access in an interior scope. Let bindings are often helpful here. For example, you could do something like

(defn getVals []
 (let [user-input (fn [message]
                      (println message)
                      (read-line))
       name (user-input "Enter Name: ")
       race (user-input "Enter Class: ")
       look (user-input "Enter Look: ")]
 (merge pc {:name name :race race :look look})))

The effect here is that the bindings are available within the scope of the let, which is everything between the opening and closing parentheses of the let. It's still immutable, though; if the return value was, say,

{:name (str name "another-name") :race name}

the second reference to name would still refer to the original value. Also note the use of an anonymous helper function here. It all depends on whether you expect to use functionality elsewhere, of course, but it can be helpful to have helper functions that don't add to the things you have to keep track of in a top-level namespace.

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.