apply函数是递归的吗?

时间:2017-03-21 11:23:42

标签: scala

考虑以下特质定义:

sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

object Stream {

  def cons[A](hd: => A, t1: => Stream[A]): Stream[A] = {
    lazy val head = hd
    lazy val tail = t1
    Cons(() => head, () => tail)
  }

  def empty[A]: Stream[A] = Empty

  def apply[A](as: A*): Stream[A] =
    if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*))

}

由于apply期望varargs作为参数,我可以创建一个新的Stream,如:

val s = Stream(2,3,4,5)

我对ADT Stream

的问题很少
  1. apply函数是递归调用吗?我试着调试代码, 但它只进行一次。

  2. 当我有一个定义val s = Stream(2,3,4,5)时,我该怎么打电话     它的头?我试过s.h但是我遇到了编译错误。

3 个答案:

答案 0 :(得分:3)

是的,它是递归的,因为它在apply的尾部调用了as。但是,由于Cons中的ht是仅按需评估的函数,因此您只在调试器中输入了一次方法,因为尾部尚未评估。

您可以使用模式匹配

获得s的头部
val h = s match {
  case Cons(h,t) => h
}

h的类型为() => Integer,因此要获得整数,您必须对其进行评估。

h()

答案 1 :(得分:1)

Stream是一个惰性集合的实现:它的头部和尾部仅在被调用时才被计算。

这就是Cons的属性属于() => T类型的原因,即零元素的函数。

apply函数将其头部定义为varargs的第一个元素,并将其尾部定义为应用于varags尾部的相同函数。但是,由于它们仅在需要时进行评估,因此您对apply(1, 2, 3)的调用不会调用tail函数,因此您看不到递归调用。

由于Cons的参数是零参数函数,因此需要调用它们来获取它们的值。因此,为了获得Stream的头部,juste调用s.h()

答案 2 :(得分:1)

仅查看apply方法签名,可以认为我们在这里处理常规递归:

def apply[A](as: A*): Stream[A] =
  if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*))

让我们分析一下签名:

def cons[A](hd: => A, t1: => Stream[A]): Stream[A]

正如您所看到的,这两个参数都是"按名称呼叫"。这意味着通过的表达不会立即进行评估。在引擎盖下,scala编译器将它们扩展为不带参数的函数。可以在程序中的任意位置调用此函数。

关于cons方法,请考虑以下两行:

lazy val head = hd
lazy val tail = t1

此处也未进行任何评估。 让我们假设我们不执行memoization,这样我们可以明确地将cons参数替换为Cons构造函数调用,这导致以下结果:

Cons(h: () => hd, t: () => t1)

现在感谢"呼唤名字"我们可以写:

Cons(h: () => as.head, t: () => apply(as.tail:_*))

以下内容不是有效的scala代码,但会解释发生了什么。现在很明显,仅当有人在t()实例上显式调用Cons函数时,才会执行递归调用apply方法。 这种方法有很多好处,最好的方法是避免堆栈溢出。你知道为什么吗?