【问题标题】:Clojure: Create a map from user inputClojure:根据用户输入创建地图
【发布时间】:2015-01-10 16:16:07
【问题描述】:

我目前正在学习 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 方式实现我想要实现的目标,为什么它只更新最后一个地图元素?地图类型合适吗?

非常感谢!

【问题讨论】:

    标签: types clojure


    【解决方案1】:

    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]]))
    

    【讨论】:

    • 我一直在尝试彻底理解这段代码。是reduce 函数多次调用prompt,对吗?如果我需要一个持久变量,我可以交换!我想它变成了一个新的原子?
    【解决方案2】:

    您的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 和一般的函数式编程中将其保持在绝对最小值。本案似乎不需要。

    【讨论】:

      【解决方案3】:

      要补充前面的答案,您不必在 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的左括号和右括号之间的所有内容。不过,它仍然是不可变的;如果返回值是,比如说,

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

      对 name 的第二次引用仍将引用原始值。另请注意此处使用匿名辅助函数。当然,这完全取决于您是否希望在其他地方使用功能,但是拥有不会添加到您必须在顶级命名空间中跟踪的内容的辅助函数会很有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-12
        • 2021-08-30
        • 1970-01-01
        • 1970-01-01
        • 2015-08-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多