这个Clojure程序有什么问题?

5

我最近开始阅读 Paul Grahams 的《On Lisp》,并学习 Clojure,所以这里可能有一些非常明显的错误,但我看不出来:(这是一个项目欧拉问题,显然)

(ns net.projecteuler.problem31)

(def paths (ref #{}))

; apply fun to all elements of coll for which pred-fun returns true
(defn apply-if [pred-fun fun coll]
  (apply fun (filter pred-fun coll)))

(defn make-combination-counter [coin-values]
  (fn recurse
    ([sum] (recurse sum 0 '()))
    ([max-sum current-sum coin-path]
      (if (= max-sum current-sum)
          ; if we've recursed to the bottom, add current path to paths
          (dosync (ref-set paths (conj @paths (sort coin-path))))
          ; else go on recursing
          (apply-if (fn [x] (<= (+ current-sum x) max-sum))
              (fn [x] (recurse max-sum (+ x current-sum) (cons x coin-path)))
              coin-values)))))

(def count-currency-combinations (make-combination-counter '(1 2 5 10 20 50 100 200)))
(count-currency-combinations 200)

当我在REPL中运行最后一行代码时,我会出现错误:
<#CompilerException java.lang.IllegalArgumentException: Wrong number of args passed to: problem31$eval--25$make-combination-counter--27$recurse--29$fn (NO_SOURCE_FILE:0)>

除了问题在哪里这个问题之外,更有趣的问题是:如何调试它?错误信息并不是很有用,我还没有找到一种好的单步调试Clojure代码的方法,也不能每次遇到问题都去在Stack Overflow上问。


非常好的问题。错误信息可能会非常敌对! - Timothy Pratley
2个回答

13

以下是三个可以让你更轻松的提示:

  1. Wrong number of args passed to: problem31$eval--25$make-combination-counter--27$recurse--29$fn (NO_SOURCE_FILE:0)> 该错误信息告诉你大致发生错误的位置:$fn 表示匿名函数, 并告诉你它是在被声明在 make-combination-counter 中的 recurse 内部。还有两个匿名函数可供选择。

  2. 如果你将源代码保存到文件中并作为脚本执行,它将会给你一个带有文件中行号的完整的堆栈跟踪。

at net.projecteuler.problem31$apply_if__9.invoke(problem31.clj:7)

注意,您还可以通过检查*e来自REPL中最后一个异常和堆栈跟踪,例如: (.stackTrace *e) 堆栈跟踪起初看起来很令人生畏,因为它会抛出所有Java内部内容。 您需要学会忽略那些只查找与您的代码相关的行。在您的情况下,这非常容易,因为它们都以net.projecteuler开头。

  • 您可以为匿名函数命名,以帮助更快地识别它们:

    (fn check-max [x] (<= (+ current-sum x) max-sum))
    
    在您的情况下,使用所有这些信息,您可以看到apply-if将一个单参数函数作为fun传递。Apply会执行这个操作:(f [1 2 3]) -> (f 1 2 3)。根据您的评论,您需要的是map。(map f [1 2 3]) -> (list (f 1) (f 2) (f 3))。当我用 map 替换 apply 后,程序似乎可以工作。
    最后,如果您想检查值,您可能需要查看clojure-contrib.logging,其中有一些有关此效果的助手。有一个spy宏,允许您包装一个表达式,它将返回完全相同的表达式,因此不会影响您函数的结果,但会打印出EXPR = VALUE,这可能很方便。此外,在该组中,各种人已发布了完整的跟踪解决方案。还有可靠的println。但是关键的技能在于能够精确定位哪些地方出了问题。一旦您知道了问题所在,通常很明显为什么会出现问题,但有时候需要打印输出来确定输入是什么。

  • 2

    我手头没有REPL,但看起来:

    (defn apply-if [pred-fun fun coll]
      (apply fun (filter pred-fun coll)))
    

    接受一个类似于列表的参数 '(1 2 3 4 5),过滤掉其中的一些元素,得到 '(1 3 5),然后创建一个函数调用,形如 (fun 1 3 5)

    看起来它被调用为 (apply-if (fn [x],并且使用了一个希望将数字列表作为单个参数接收的函数。

    可以将 apply-if 函数更改为直接将 fun 调用(不使用 apply),或者将调用 fun 的方式更改为接受任意数量的参数的函数。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接