为什么只有当函数不使用序列破坏时才会发生StackOverflowError?

时间:2017-11-27 23:55:08

标签: clojure

以下代码在problem 19的4closure网站上运行。

我写的函数应该返回序列中的最后一个元素。

此定义发生了java.lang.StackOverflowError:

(fn my-last [lst]
  (if (rest lst)
    (my-last (rest lst))
    (first lst)))

但是当我运行以下定义时,它运行良好:

(fn my-last [[x & xs]]
  (if xs
    (my-last xs)
    x))

上述两个区块的唯一区别似乎是参数列表中破坏性序列绑定 使用解构

  • 那么第一个定义怎么会抛出错误?
  • 我失踪的这两个功能有什么不同吗?

编辑:在第一个函数定义中修复拼写错误

2 个答案:

答案 0 :(得分:2)

以下是答案:

(rest [1 2 3]) => (2 3)
(rest [3]) => ()
(next [3]) => nil

使用rest会返回空序列(),在测试中评估为true。当没有更多项目时,使用next会返回nil。由于Clojure认为nilfalse相同,这将使其有效。

由于许多人在这一点上被绊倒,我宁愿选择更明确的测试,例如:

(if-not (empty? ...))

或类似。

更新

以下是我写它的方式。测试内容is from the Tupelo library

(ns tst.demo.core
  (:use tupelo.test)
  (:require
    [tupelo.core :as t]))

(defn get-last
  [items]
  (when (empty? items)
    (throw (IllegalArgumentException. "get-last: items must not be empty")))
  (let [others (rest items)]
    (if (empty? others)
      (first items)
      (get-last others))))

(dotest
  (throws? (get-last []))
  (is= 1 (get-last [1]))
  (is= 2 (get-last [1 2]))
  (is= 3 (get-last [1 2 3])))

有些人会坚持认为上面的例子不够“纯粹”,但我认为明确的清晰度每次都会超出隐含的行为。

更新#2

如有疑问,请询问代码正在做什么:

(defn my-last
  [[x & xs]]
  (t/spyx {:x x :xs xs})
  (if xs
    (t/spyx (my-last xs))
    (t/spyx x)))
(dotest
  (t/spyx (my-last [1 2 3])))

结果:

{:x x, :xs xs} => {:x 1, :xs (2 3)}
{:x x, :xs xs} => {:x 2, :xs (3)}
{:x x, :xs xs} => {:x 3, :xs nil}
x => 3
(my-last xs) => 3
(my-last xs) => 3
(my-last [1 2 3]) => 3

你有答案。

答案 1 :(得分:1)

TL; DR回答: 第一个版本是创建无限循环,因为if语句永远不会为假,因为(rest [x])(rest [])会返回真值

专业提示:以下是打破空序列的一些有趣方法

empty?

seq

(zero? (count coll))

最常用的方法是使用seq进行解构:

(fn my-last [[x & xs]]
 (if (seq xs)
   (my-last xs)
    x))

但您也可以将原始解决方案包裹在seq中并且它可以有效:

(fn my-last [lst]
  (if (seq (rest lst))
    (my-last (rest lst))
    (first lst)))

(seq (rest lst))将返回nil,当lst只有一个您正在检查的元素时,这是一个假值。

附注,另一种解决方法是

  

!得到反向集合的第一个元素,因为问题19只有last被禁止

     

(comp reverse first)