【问题标题】:Clojure : function returns nil rather an evaluated expressionClojure :函数返回 nil 而不是评估表达式
【发布时间】:2015-03-31 23:42:45
【问题描述】:

我编写了一个函数,可以对列表的成员求和。非常简单,新手的东西,但由于某种原因它返回 nil,我不知道为什么:这应该是不费吹灰之力。实现和函数调用如下。

实现:

(defn add-list-members
  ([x] (add-list-members x 0))
  ([x total]
   (if (= (count x) 2)
     (+ (first x) (first (rest x)) total))
   (if (= (count x) 1)
     (+ (first x) total))
   (if (> (count x) 2)
     (recur (rest (rest x)) (+ (+ (first x) (first (rest x))) total)))))

函数调用:

(println "our total is: " (add-list-members '(2 23 33 14 33 134 9000 98993)))

同时,我有一个添加了打印语句的调试版本,并且我已经能够验证实现是否正常工作。

调试版本:

(defn add-list-members-debug
  ([x] (add-list-members-debug x 0))
  ([x total]
   (println ">>> our list now: " x)
   (println ">>> our total now: " total)
   (if (= (count x) 2)
     (println "outcome 2 : " (+ (first x) (first (rest x)) total)))
   (if (= (count x) 1)
     (println "outcome 1 : " (+ (first x) total)))
   (if (> (count x) 2)
     (recur (rest (rest x)) (+ (+ (first x) (first (rest x))) total)))))

调用调试版本:

(add-list-members-debug '(2 23 33 14 33 134 9000 98993))

我使用的开发工具是插入 Leiningen REPL 的 Vim。任何关于我在这里做错的提示将不胜感激。

【问题讨论】:

  • 您的意思是“null”而不是“nil”吗?
  • 问题版本的输出是:"our total is: nil"
  • 不,nil 是 Clojure 在没有其他内容可返回时经常返回的内容。
  • 另一个在 Clojure 中使用 vim 的人!现在我知道我们至少有两个人。 :-)

标签: clojure functional-programming


【解决方案1】:

您以错误的方式使用了if。考虑以下代码:我特意简化了代码以明确发生了什么。

(defn xxx [v]
  (if (= (count v) 1) "one")
  (if (= (count v) 2) "two")
  (if (> (count v) 2) "more"))

如果你运行(xxx [1]) 会发生什么?也许您期望代码将返回"one",然而,它返回nil。为什么?

函数返回其最后一个表达式的值,在本例中为最后一行(if (> (count v) 2) "more")。如果传递[1],则第一个if 表达式返回"one",它被忽略。第二个if 将返回nil,它也被忽略。第三个也是最后一个会返回nil,也就是函数的返回值。

也许计算元素和的最简单方法是:

(defn add-elements [xs] (apply + xs))

或

(defn add-elements [xs] (reduce + xs))

您的代码可以以更简单的方式重新实现:

(defn add-elements
  ([xs] (add-elements xs 0))
  ([xs total] (if (empty? xs) total
                (recur (rest xs) (+ total (first xs))))))

【讨论】:

  • 非常感谢您的 cmets 和简化实现的说明。
【解决方案2】:

括号是错误的,或者至少是次优的,并且您没有返回total 的基本情况。 (关于括号:您的缩进,在括号中是正确的,表明:每个if 都在同一级别上;它们都不在“else”位置。)这是一个正确的版本:

(defn add-list-members
  ([x] (add-list-members x 0))
  ([x total]
   (if (= (count x) 0)
     total
     (if (= (count x) 2)
       (+ (first x) (first (rest x)) total)
       (if (= (count x) 1)
         (+ (first x) total)
         (if (> (count x) 2)
           (recur (rest (rest x)) (+ (+ (first x) (first (rest x))) total))))))))

您得到nil 作为返回值,因为当最后一个if 测试失败时,它转到else 子句,在这种情况下是一个列表的空端,即nil。

但是,多个ifs 可以替换为一个cond。您还可以使用ffirst 代替(first (first,使用fnext 代替(first (rest,以及使用nnext 代替(rest (rest。使用诸如empty? 之类的谓词而不是检查count 是否等于0 也会更惯用;我使用count 作为基本案例只是为了保持与现有代码的并行性。最简单的版本可能是:

(defn add-list-members [x] (apply + x))

您也可以使用reduce 来创建一个非常简单的版本。不过,您可能只是想尝试一下recur。

(好吧,您可以在不使用嵌入的 else 子句的情况下做到这一点,就像您原来的那样,但我不会推荐它,您仍然需要空序列基本情况。当您在函数定义中放置多个单独的表达式,它们中的每一个总是被执行。这是低效的,我觉得容易出错。在创建副作用时,在函数定义中使用多个单独的表达式是有意义的——例如添加用于调试的打印语句。)

【讨论】:

  • 好的——明白了。非常感谢您的解释和指点。
  • 是的,我想我错误地认为函数返回对于通过的 if 测试是隐含的。我真的没有权利这么想,但有时我很容易产生一厢情愿的想法。再次感谢。
  • 还要注意fnext 只是second。我想我可能会在 ffirst 也相关的某些情况下使用它,但通常它是一个非常罕见的函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多