scala.collection.immutable.List [Int]与自定义List构造函数List [Int]

时间:2018-08-14 20:45:16

标签: scala list constructor

我是scala的新手。要温柔。 我认为问题在于我创建了一个与scala.collection.immutable.List

冲突的特质列表
trait List[T] {
  def isEmpty: Boolean
  def head[T]
  def tail[T]
}

class Cons[T](val head:T, val tail: Option[List[T]]) extends List[T] {
  def isEmpty: Boolean = false
  def head[T] = head
  def tail[T] = tail
}

class Nil[T] extends List[T] {
  def isEmpty: Boolean = true
  def head[T] = throw new IllegalStateException("head")
  def tail[T] = throw new IllegalStateException("tail")
}

// ERROR HERE
// obj with one elem
val one: Cons[Int] = new Cons[Int](1,Option(scala.List[Int]()))
  

错误是:预期的scala.Option [List [Int]],实际   scala.Option [scala.collection.immutable.List [Int]]

谢谢。

3 个答案:

答案 0 :(得分:2)

您已声明tail的类型为Option[List[T]],但是您将调用Option(scala.List[Int]())的结果传递给了它,这是Option.apply(scala.List[Int].apply())的语法糖。

scala.List[Int].apply返回scala.collection.immutable.List[Int],而不是List[Int]。我不确定为什么您会期望使用内置的Scala方法来返回一个完全不了解的对象。无论如何,针对此特定错误的正确解决方案是传递您的 Nil类的实例,而不是尝试传递 Scala的 Nil。 / p>

这与您为性状选择名称List无关。您使用其他任何名称都将遇到相同的错误。


让我们仔细看看您的代码:

trait List[T] {
  def isEmpty: Boolean
  def head[T]
  def tail[T]
}

您没有为headtail声明类型。这意味着将要使用的默认类型为Unit,这意味着这些方法不会返回任何内容,它们只会产生副作用。因此,以下内容完全合法:

class Cons extends List[Int] {
  override def isEmpty = false
  override def head[Int] = Unit
  override def tail[Int] = Unit
}

实际上,由于返回类型为Unit意味着返回值只是被丢弃,即使这是合法的:

class Cons extends List[Int] {
  override def isEmpty = false
  override def head[Int] = "Hello"
  override def tail[Int] = true
}

所以,您可能想要声明返回类型:

trait List[T] {
  def isEmpty: Boolean
  def head[T]: T
  def tail[T]: T
}

另一个问题是,您要使用类型参数T声明一个特征,然后使用一个不同类型参数声明一个方法head,而恰好具有相同的特征名称,并因此遮蔽外部T,并声明具有另一个不同类型参数的方法tail

这完全等同于

trait List[T] {
  def isEmpty: Boolean
  def head[U]
  def tail[V]
}

三个类型参数之间的关系完全为零。

这意味着例如以下类完全合法:

class Cons extends List[Int] {
  override def isEmpty = false
  override def head[String] = "Hello"
  override def tail[Float] = 1.23
}

事实上,由于您从未在任何地方使用T,因此可以将其省略:

trait List {
  def isEmpty: Boolean
  def head[U]
  def tail[V]
}

对于UV同样如此,因此您实际上 所写的内容或多或少是这样的:

trait List {
  def isEmpty: Boolean
  def head
  def tail
}

但是我想那根本不是您想要的!

似乎您实际上希望所有三个T都相同。但!因为head是单个元素而tail是一个列表(或者在您的情况下是可选列表),所以这可能行不通……单个元素和列表选项如何才能具有完全相同的类型?那是不可能的!

您(可能)想要的是这样的东西:

trait List[T] {
  def isEmpty: Boolean
  def head: T
  def tail: Option[List[T]]
}

但是请注意,Option也没有意义,因为您以后实际上创建了一个特殊类来表示一个空列表(Nil),因此您不需要Option在这里。这将造成极大的混乱,因为您既可以有一个现有的空列表,也可以有一个不存在的列表,但是它们都具有相同的含义。让我们摆脱Option

trait List[T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
}

最后,您会发现我们只能从列表中的 中删除元素,而永远不能在中放入。 (我们只能构造具有其他元素的 new 列表。)这意味着我们的列表实际上是其元素类型中的 covariant ,因此,请确保Scala知道这一点:

trait List[+T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
}

此外,请注意,您要确保只有两个子类:空列表和非空列表。您不想有第三种列表。因此,应将其设置为sealed trait,以便只能在同一编译单元内对其进行子类化:

sealed trait List[+T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
}

现在,让我们看一下Cons类。

class Cons[T](val head:T, val tail: Option[List[T]]) extends List[T] {
  def isEmpty: Boolean = false
  def head[T] = head
  def tail[T] = tail
}

同样,您在这里也有同样的阴影。您不需要三个不同的类型参数,而只需要一个:

class Cons[+T](val head: T, val tail: Option[List[T]]) extends List[T] {
  def isEmpty = false
  def head = head
  def tail = tail
}

还请注意,在您的原始版本中,headtail的返回类型均为Unit,因此返回值将被丢弃。

再次,让我们摆脱Option,因为我们知道,总会有一个tail,这就是我们Nil的目的,代表一个空的tail

class Cons[+T](val head: T, val tail: List[T]) extends List[T] {
  def isEmpty = false
  def head = head
  def tail = tail
}

此外,构造函数中的val声明已经创建了一个getter方法,因此我们可以摆脱这些方法:

class Cons[+T](override val head: T, override val tail: List[T]) extends List[T] {
  override def isEmpty = false
}

还请注意,我们并未在isEmpty中进行任何计算,因此无需将其作为一种方法来每次重新评估false;我们可以简单地将其设置为字段:

class Cons[T](override val head: T, override val tail: List[T]) extends List[T] {
  override val isEmpty = false
}

同样,我们不希望有人创建Cons的另一个子类,因为我们要确保只有两个。因此,我们将Cons设为final类:

final class Cons[+T](override val head: T, override val tail: List[T]) extends List[T] {
  override val isEmpty = false
}

为方便起见,我们也将其设置为case class,这样我们就可以对构造函数参数使用模式匹配,并获得toString()##的一个很好的自动定义的实现,并且带有apply工厂方法的伴随对象:

final case class Cons[+T](override val head: T, override val tail: List[T]) extends List[T] {
  override val isEmpty = false
}

最后,所有这些同样适用于Nil,我将不再重复相同的详细步骤:

final case class Nil[+T]() extends List[T] {
  override val isEmpty = true
  override def head: Nothing = throw new IllegalStateException("head")
  override def tail: Nothing = throw new IllegalStateException("tail")
}

但是,请注意,没有“整数空列表”,“字符串空列表”等是没有意义的。空列表是空列表。期。空列表只有一个。

因此,让我们将其设为对象:

case object Nil extends List[Nothing] {
  override val isEmpty = true
  override def head = throw new IllegalStateException("head")
  override def tail = throw new IllegalStateException("tail")
}

之所以可行,是因为我们的列表是协变的(如果List[X]List[Y]的子类型,则XY的子类型)和{{1} }是每种类型的子类型;因此Nothing是每个List[Nothing]的子类型,因此我们可以在期望List[T]的任何地方替换Nil,而不管List[T]是什么。

最后,我们可能想为T随播对象提供List工厂方法,以方便地构造apply

List

总共,我们的代码现在看起来像这样:

object List {
  def apply[T](elements: T*) = elements.foldRight(Nil: List[T])(Cons.apply)
}

答案 1 :(得分:1)

我认为您想要的是:

 val one: Cons[Int] = new Cons[Int](1, None)

或者,如果您希望包含多个元素的列表:

val two: Cons[Int] = new Cons[Int](1, Some(new Cons[Int](2, None)))

答案 2 :(得分:0)

好的,我相信(因为我以某种方式覆盖了类List),我应该一直使用自定义的“构造函数”。所以这是正确的代码行:

val one: Cons[Int] = new Cons[Int](1,Option(new Nil[Int]()))