【问题标题】:Very simple practice program won't compile非常简单的练习程序不会编译
【发布时间】:2019-07-29 17:35:25
【问题描述】:

我目前正在阅读“Clojure for the Brave and True” 在当前章节中,他们正在解释一个程序,该程序采用代表霍比特人身体部位的哈希图向量。由于仅以不对称方式提供带有零件的列表(仅左臂、左眼等是其中的一部分),因此有必要编写一个添加相应右侧零件的函数。后来有一个练习来扩展这个函数以获取一个数字并为每个左边的身体部位添加该数量的身体部位。第二个函数会随机选择一个身体部位。

这是我的代码:

(ns clojure-noob.core
  (:gen-class)
  (:require [clojure.string :as str] ))


(def asym-hobbit-body-parts [{:name "head" :size 3}
                             {:name "left-eye" :size 1}
                             {:name "left-ear" :size 1}
                             {:name "mouth" :size 1}
                             {:name "nose" :size 1}
                             {:name "neck" :size 2}
                             {:name "left-shoulder" :size 3}
                             {:name "left-upper-arm" :size 3}
                             {:name "chest" :size 10}
                             {:name "back" :size 10}
                             {:name "left-forearm" :size 3}
                             {:name "abdomen" :size 6}
                             {:name "left-kidney" :size 1}
                             {:name "left-hand" :size 2}
                             {:name "left-knee" :size 2}
                             {:name "left-thigh" :size 4}
                             {:name "left-lower-leg" :size 3}
                             {:name "left-achilles" :size 1}
                             {:name "left-foot" :size 2}])


(defn make-sym-parts [asym-set num]
  (reduce (fn [sink, {:keys [name size] :as body_part}]
           (if (str/starts-with? name "left-")
             (into sink [body_part 
                         (for [i (range num)]
                           {:name (str/replace name #"^left" (str i))
                            :size size})])
             (conj sink body_part)))
           []
           asym-set))


(defn rand-part [parts]
  (def size-sum (reduce + (map :size parts)))
  (def thresh (rand size-sum))
  (loop [[current & remaining] parts
         sum (:size current)]
    (if (> sum thresh)
      (:name current)
      (recur remaining (+ sum (:size (first remaining)))))))


(defn -main
  "I don't do a whole lot ... yet."
  [arg]
  (cond 
    (= arg "1") (println (make-sym-parts asym-hobbit-body-parts 3))
    (= arg "2") (println (rand-part asym-hobbit-body-parts))
    (= arg "3") (println (rand-part (make-sym-parts asym-hobbit-body-parts 3)))))

所以

lein run 1

工作并打印出扩展的向量

lein run 2

也可以工作并打印出随机的身体部位名称。

但是:

lein run 3

会产生以下错误:

861 me@ryzen-tr:~/clojure_practice/clojure-noob$ lein run 3
862 Exception in thread "main" Syntax error compiling at (/tmp/form-init15519101999846500993.clj:1:74).
863         at clojure.lang.Compiler.load(Compiler.java:7647)
864         at clojure.lang.Compiler.loadFile(Compiler.java:7573)
865         at clojure.main$load_script.invokeStatic(main.clj:452)
866         at clojure.main$init_opt.invokeStatic(main.clj:454)
867         at clojure.main$init_opt.invoke(main.clj:454)
868         at clojure.main$initialize.invokeStatic(main.clj:485)
869         at clojure.main$null_opt.invokeStatic(main.clj:519)
870         at clojure.main$null_opt.invoke(main.clj:516)
871         at clojure.main$main.invokeStatic(main.clj:598)
872         at clojure.main$main.doInvoke(main.clj:561)
873         at clojure.lang.RestFn.applyTo(RestFn.java:137)
874         at clojure.lang.Var.applyTo(Var.java:705)
875         at clojure.main.main(main.java:37)
876 Caused by: java.lang.NullPointerException
877         at clojure.lang.Numbers.ops(Numbers.java:1068)
878         at clojure.lang.Numbers.add(Numbers.java:153)
879         at clojure.core$_PLUS_.invokeStatic(core.clj:992)
880         at clojure.core$_PLUS_.invoke(core.clj:984)
881         at clojure.lang.ArrayChunk.reduce(ArrayChunk.java:63)
882         at clojure.core.protocols$fn__8139.invokeStatic(protocols.clj:136)
883         at clojure.core.protocols$fn__8139.invoke(protocols.clj:124)
884         at clojure.core.protocols$fn__8099$G__8094__8108.invoke(protocols.clj:19)
885         at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:27)
886         at clojure.core.protocols$fn__8131.invokeStatic(protocols.clj:75)
887         at clojure.core.protocols$fn__8131.invoke(protocols.clj:75)
888         at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
889         at clojure.core$reduce.invokeStatic(core.clj:6824)
890         at clojure.core$reduce.invoke(core.clj:6810)
891         at clojure_noob.core$rand_part.invokeStatic(core.clj:39)
892         at clojure_noob.core$rand_part.invoke(core.clj:38)
893         at clojure_noob.core$_main.invokeStatic(core.clj:54)
894         at clojure_noob.core$_main.invoke(core.clj:48)
895         at clojure.lang.Var.invoke(Var.java:384)
896         at user$eval140.invokeStatic(form-init15519101999846500993.clj:1)
897         at user$eval140.invoke(form-init15519101999846500993.clj:1)
898         at clojure.lang.Compiler.eval(Compiler.java:7176)
899         at clojure.lang.Compiler.eval(Compiler.java:7166)
900         at clojure.lang.Compiler.load(Compiler.java:7635)
901         ... 12 more

我不知道为什么会这样。谷歌搜索错误的第一行也不会显示有用的信息。有谁知道这个问题吗?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    问题是您返回了一个混合类型的向量。有些元素是地图,有些是列表。注意make-sym-parts 的前几个条目:

    (make-sym-parts asym-hobbit-body-parts 3)
    
    =>
    [{:name "head", :size 3}
     {:name "left-eye", :size 1}
     ({:name "0-eye", :size 1} {:name "1-eye", :size 1} {:name "2-eye", :size 1})
      . . .
    

    看看我在这里列出的最后一个条目。这不是地图;这是一个地图列表。当您尝试将:size 应用于列表时,您会得到nil

    (:size '({:name "0-eye", :size 1} {:name "1-eye", :size 1} {:name "2-eye", :size 1}))
    
    => nil
    

    当您将:size 映射到整个列表时,您会得到:

    (->> (make-sym-parts asym-hobbit-body-parts 3)
         (map :size))
    
    => (3 1 nil 1 nil 1 1 2 3 nil 3 nil 10 10 3 nil 6 1 nil 2 nil 2 nil 4 nil 3 nil 1 nil 2 nil)
    

    这导致了一个问题,因为您通过reduce 将这些值提供给+,如果您给它一个nil+ 将正确地抛出一个合适的值,因为nil 不是一个数字.


    那么,解决方法是什么?老实说,我已经快 3 个月没有写 Clojure 了,所以我开始生疏了,而且我还没有阅读问题说明,但看​​起来你只需要把这个列表弄平:

    (defn make-sym-parts [asym-set num]
      (reduce (fn [sink, {:keys [name size] :as body_part}]
               (if (str/starts-with? name "left-")
                 (into sink (conj  ; I threw in a call to conj here and rearranged it a bit
                             (for [i (range num)]
                               {:name (str/replace name #"^left" (str i))
                                :size size})
                             body_part))
    
                 (conj sink body_part)))
    
              []
    
              asym-set))
    
    (make-sym-parts asym-hobbit-body-parts 3)
    
    =>
    [{:name "head", :size 3}
     {:name "left-eye", :size 1}
     {:name "0-eye", :size 1}
     {:name "1-eye", :size 1}
     {:name "2-eye", :size 1}
     {:name "left-ear", :size 1}
     {:name "0-ear", :size 1}
     {:name "1-ear", :size 1}
     {:name "2-ear", :size 1}
     {:name "mouth", :size 1}
     {:name "nose", :size 1}
     {:name "neck", :size 2}
     {:name "left-shoulder", :size 3}
     {:name "0-shoulder", :size 3}
     {:name "1-shoulder", :size 3}
     {:name "2-shoulder", :size 3}
     {:name "left-upper-arm", :size 3}
     {:name "0-upper-arm", :size 3}
     {:name "1-upper-arm", :size 3}
     {:name "2-upper-arm", :size 3}
     {:name "chest", :size 10}
     {:name "back", :size 10}
     {:name "left-forearm", :size 3}
     {:name "0-forearm", :size 3}
     {:name "1-forearm", :size 3}
     {:name "2-forearm", :size 3}
     {:name "abdomen", :size 6}
     {:name "left-kidney", :size 1}
     {:name "0-kidney", :size 1}
     {:name "1-kidney", :size 1}
     {:name "2-kidney", :size 1}
     {:name "left-hand", :size 2}
     {:name "0-hand", :size 2}
     {:name "1-hand", :size 2}
     {:name "2-hand", :size 2}
     {:name "left-knee", :size 2}
     {:name "0-knee", :size 2}
     {:name "1-knee", :size 2}
     {:name "2-knee", :size 2}
     {:name "left-thigh", :size 4}
     {:name "0-thigh", :size 4}
     {:name "1-thigh", :size 4}
     {:name "2-thigh", :size 4}
     {:name "left-lower-leg", :size 3}
     {:name "0-lower-leg", :size 3}
     {:name "1-lower-leg", :size 3}
     {:name "2-lower-leg", :size 3}
     {:name "left-achilles", :size 1}
     {:name "0-achilles", :size 1}
     {:name "1-achilles", :size 1}
     {:name "2-achilles", :size 1}
     {:name "left-foot", :size 2}
     {:name "0-foot", :size 2}
     {:name "1-foot", :size 2}
     {:name "2-foot", :size 2}]
    

    在对map 的调用中,您还可以检查元素是地图还是列表。如果是列表,您可以在子列表上再次调用(map :size。这取决于您是否想要一个平面列表或嵌套列表作为最终结果。您也可以使用mapcat 来获得一个平面列表,但您需要处理不是地图的条目。


    您如何从那个(非常冗长的)堆栈跟踪中找出问题所在?一旦您意识到错误的解构和错误的键查找(如我上面描述的)返回nil,推理就变得容易得多。每当您获得 NPE 时,可以安全地假设您正在解构错误或使用错误的键进行查找。这些并不是 NPE 的唯一原因,但根据我在 Clojure 的经验,它们是最常见的。

    从上到下读取堆栈跟踪以跟踪不良数据的来源以及读取使用的位置。请注意我的 cmets 以获取有关如何阅读它的提示:

    ; If you have an NPE, that means you have a nil being passed somewhere...
    Caused by: java.lang.NullPointerException
    877         at clojure.lang.Numbers.ops(Numbers.java:1068)
    878         at clojure.lang.Numbers.add(Numbers.java:153)
    
                ; ... so, you're passing a nil to + ("_PLUS_") 
    879         at clojure.core$_PLUS_.invokeStatic(core.clj:992)
    880         at clojure.core$_PLUS_.invoke(core.clj:984)
    881         at clojure.lang.ArrayChunk.reduce(ArrayChunk.java:63)
    882         at clojure.core.protocols$fn__8139.invokeStatic(protocols.clj:136)
    883         at clojure.core.protocols$fn__8139.invoke(protocols.clj:124)
    884         at clojure.core.protocols$fn__8099$G__8094__8108.invoke(protocols.clj:19)
    885         at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:27)
    886         at clojure.core.protocols$fn__8131.invokeStatic(protocols.clj:75)
    887         at clojure.core.protocols$fn__8131.invoke(protocols.clj:75)
    888         at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
    
                ; ... and it's happening inside a call to reduce 
    889         at clojure.core$reduce.invokeStatic(core.clj:6824)
    
                ; ... and that call to reduce is happening inside of rand-part
    891         at clojure_noob.core$rand_part.invokeStatic(core.clj:39)
    892         at clojure_noob.core$rand_part.invoke(core.clj:38)
    893         at clojure_noob.core$_main.invokeStatic(core.clj:54)
    

    您只有一次这样的+ 实例在rand-part 内部被传递给reduce,所以这是一个开始寻找的好地方。从那里,您只需要使用标准调试技术跟踪 nil 的来源。

    这里的要点是扫描堆栈跟踪以尝试找到您识别的单词。不幸的是,由于 Clojure 名称在翻译成 Java 时会被“破坏”,因此名称往往非常冗长和嘈杂。你只需要学会“看穿噪音”来找到相关信息。稍微练习一下就容易了。



    其他一些注意事项:

    • 不要在defn 中使用def。 def 创建不受范围约束的全局变量。请改用let

    • 尝试使用更多缩进。缩进的单个空格不是很好。

    • Clojure 使用破折号。您在大多数情况下都在使用它,但 body_part 似乎是 Python 的回归。

    如果您愿意,您可以在 Code Review 上发布此代码,我们可以提出建议来帮助您改进它。

    【讨论】:

    • 我认为这是我在 Stackoverflow 上得到的最有帮助的答案,谢谢。以及有关如何改进代码的信息。我肯定会使用我还不知道的 codereview,所以也谢谢你:D
    • @Uzaku 没问题。乐意效劳。我会看看我以后能不能得到一个评论。如果不是我,我相信其他人会跳上它。我们没有太多的 Clojure 需要审查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多