我现在正在学习Clojure,而且我很难进入功能性思维。
我有一张我试图从命令行填充的地图。这是我到目前为止的代码:
(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))
但是它只更新最后一个条目(:Look)。
问题: 如何以更实用的Clojure-y方式实现我想要实现的目标,以及它如何只更新最后一个map元素?地图是正确使用的类型吗?
非常感谢提前!
答案 0 :(得分:3)
assoc
返回包含新映射的新地图。您没有将这些新地图分配给任何内容,因此它们将被丢弃。您需要跟踪中间地图 - 从输入序列重复更新状态的一种方法是使用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]]))
答案 1 :(得分:1)
您的getVals
函数正在创建三个新地图并仅返回最后一个地图。在功能上与命令性思维中最重要的部分之一是创建新版本的值而不是修改它们。
另一种方法是使用三个新项目创建一个新地图,并将其合并到起始地图中以获得所需的地图:
(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))}))
另一个答案中提到的减少也会起作用,但此时它可能是一个不必要的额外概念。我认为从简单的例子中学习减少更好。
你也可以使用一个原子,但这是一个可变的状态,但我们鼓励它在Clojure和函数式编程中保持绝对最小值。这种情况似乎并不需要它。
答案 2 :(得分:1)
要添加到之前的答案,您不必在Clojure中使用可变状态来获取可在内部范围内访问的内容。让绑定在这里经常有用。例如,您可以执行类似
的操作(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})))
这里的效果是绑定在let的范围内可用,它是let的开括号和右括号之间的所有内容。但它仍然是不变的;如果返回值是,比方说,
{:name (str name "another-name") :race name}
对name的第二个引用仍将引用原始值。另请注意此处使用匿名帮助函数。当然,这完全取决于您是否希望在其他地方使用功能,但是将辅助函数添加到您必须在顶级命名空间中跟踪的内容中会很有帮助。