【问题标题】:Why do I get NPE in the following code?为什么我在以下代码中得到 NPE?
【发布时间】:2010-07-14 14:44:41
【问题描述】:

以下代码按预期执行,但最后给出了NullPointerException。我在这里做错了什么?

(ns my-first-macro)

(defmacro exec-all [& commands]
  (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands))

(exec-all
  (cons 2 [4 5 6])
  ({:k 3 :m 8} :k)
  (conj [4 5 \d] \e \f))

; Output:
; Clojure 1.2.0-master-SNAPSHOT
; Code:  (cons 2 [4 5 6])   =>  Result:  (2 4 5 6)
; Code:  ({:k 3, :m 8} :k)  =>  Result:  3
; Code:  (conj [4 5 d] e f)     =>  Result:  [4 5 d e f]
; java.lang.NullPointerException (MyFirstMacro.clj:0)
; 1:1 user=> #<Namespace my-first-macro>
; 1:2 my-first-macro=> 

(如需正确语法高亮代码,请转至here。)

【问题讨论】:

    标签: clojure lisp macros


    【解决方案1】:

    看看正在发生的扩展:

    (macroexpand '(exec-all (cons 2 [4 5 6])))
    =>
    ((clojure.core/println "Code: " (quote (cons 2 [4 5 6])) "\t=>\tResult: " (cons 2 [4 5 6])))
    

    如您所见,展开式周围有一对额外的括号,这意味着 Clojure 尝试执行 println 函数的结果,即 nil。

    要解决这个问题,我建议修改宏以在前面包含一个“do”,例如

    (defmacro exec-all [& commands]
      (cons 'do (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands)))
    

    【讨论】:

    • 当然,您可以重写它以扩展为doseq 等。但是为什么呢?这是一个完全合理的解决方案,对现有代码的更改很少;我会说坚持下去。
    • @Michael:因为我相信了解替代方法将有助于我的 Clojure 学习。
    • 我猜另一个选择是将每个函数的输出更改为字符串而不是 println 并将它们连接在一起。我认为这是这样做的自然“无副作用”方式。
    • @Rahuλ G:很公平。我将使用基于doseq 的版本添加另一个答案。
    • ...显然是在赞成解决上述答案中显示的问题的最佳方法之后。 :-)
    【解决方案2】:

    由于 OP 要求编写此宏的其他可能方式(请参阅已接受答案的 cmets),因此:

    (defmacro exec-all [& commands]
      `(doseq [c# ~(vec (map (fn [c]
                               `(fn [] (println "Code: " '~c "=> Result: " ~c)))
                             commands))]
         (c#)))
    

    这扩展为类似

    (doseq [c [(fn []
                 (println "Code: "      '(conj [2 3 4] 5)
                          "=> Result: " (conj [2 3 4] 5)))
               (fn []
                 (println "Code: "      '(+ 1 2)
                          "=> Result: " (+ 1 2)))]]
      (c))
    

    请注意,其值将绑定到 cfn 表单在宏扩展时被收集在一个向量中。

    不用说,原始版本更简单,因此我认为(do ...) 是完美的修复。 :-)

    交互示例:

    user=> (exec-all (conj [2 3 4] 5) (+ 1 2))                                                                                                    
    Code:  (conj [2 3 4] 5) => Result:  [2 3 4 5]
    Code:  (+ 1 2) => Result:  3
    nil
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-05
      • 1970-01-01
      • 2019-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-04
      • 1970-01-01
      相关资源
      最近更新 更多