Clojure:根据用户输入创建地图
Clojure: Create a map from user input
我目前正在学习 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 方式实现我想要实现的目标,它为什么只更新最后一个地图元素?地图类型是否适合使用?
非常感谢!
assoc
returns 包含新映射的新地图。您没有将这些新地图分配给任何东西,因此它们将被丢弃。您需要跟踪中间映射 - 从一系列输入重复更新状态的一种方法是使用 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]]))
您的 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))}))
另一个答案中提到的 reduce 也可以,但此时它对您来说可能是一个不必要的额外概念。我认为从更简单的例子中学习 reduce 会更好。
您也可以使用原子,但它是可变状态,但通常在 Clojure 和函数式编程中鼓励将其保持在绝对最低限度。这种情况似乎不需要它。
为了补充前面的答案,您不必在 Clojure 中使用可变状态就可以在内部范围内访问某些内容。 Let 绑定在这里通常很有用。例如,您可以做类似
(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 左括号和右括号之间的所有内容。不过,它仍然是不可变的;如果 return 值是,比方说,
{:name (str name "another-name") :race name}
对名称的第二次引用仍将引用原始值。另请注意此处使用了匿名辅助函数。当然,这完全取决于您是否希望在其他地方使用功能,但是拥有不添加到您必须在顶级命名空间中跟踪的内容的辅助函数可能会有所帮助。
我目前正在学习 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 方式实现我想要实现的目标,它为什么只更新最后一个地图元素?地图类型是否适合使用?
非常感谢!
assoc
returns 包含新映射的新地图。您没有将这些新地图分配给任何东西,因此它们将被丢弃。您需要跟踪中间映射 - 从一系列输入重复更新状态的一种方法是使用 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]]))
您的 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))}))
另一个答案中提到的 reduce 也可以,但此时它对您来说可能是一个不必要的额外概念。我认为从更简单的例子中学习 reduce 会更好。
您也可以使用原子,但它是可变状态,但通常在 Clojure 和函数式编程中鼓励将其保持在绝对最低限度。这种情况似乎不需要它。
为了补充前面的答案,您不必在 Clojure 中使用可变状态就可以在内部范围内访问某些内容。 Let 绑定在这里通常很有用。例如,您可以做类似
(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 左括号和右括号之间的所有内容。不过,它仍然是不可变的;如果 return 值是,比方说,
{:name (str name "another-name") :race name}
对名称的第二次引用仍将引用原始值。另请注意此处使用了匿名辅助函数。当然,这完全取决于您是否希望在其他地方使用功能,但是拥有不添加到您必须在顶级命名空间中跟踪的内容的辅助函数可能会有所帮助。