Является ли :: [Int] таким же, как List [Int]? - PullRequest
2 голосов
/ 17 января 2020
scala> val w = new ::[Int](4,Nil)
w: scala.collection.immutable.::[Int] = List(4)

scala> val x = List[Int](4)
x: List[Int] = List(4)

В чем разница между этими двумя выражениями? Является ли ::[Int](4) экземпляром List[int], поскольку и ::[Int](4), и List[Int](4) дают List(4)?

Ответы [ 2 ]

3 голосов
/ 17 января 2020

::[T] является одним из конструкторов типа данных алгебра c List[T]. Другой - Nil. Рассмотрим следующее индуктивное определение List

sealed trait List[+T]
case object Nil extends List[Nothing] 
case class ::[T](head: T, tail: List[T]) extends List[T]

:: может показаться немного странным, поскольку это чисто символическое c имя, а в других языках это иногда обозначается как Cons. Конструкторы являются средством создания значений типа List[T], вот некоторые из них

Nil
::(1, Nil)
::(2, ::(1, Nil))
::(42, ::(2, ::(1, Nil)))

Эти значения, будучи индуктивными, обладают замечательным свойством всякий раз, когда мы их имеем, мы всегда знаем, как построить следующий один (и). Другими словами, индуктивные значения подразумевают индуктивные значения. Например, и

::(2, ::(1, Nil))

и

::(51, ::(1, Nil))

являются значениями ::(1, Nil) силой конструктора ::[Int].

Is ::[Int] так же, как List[Int]?

Поскольку Scala реализует алгебраические c типы данных, используя форму наследования , действительно существует is-a -подтип-из отношений

implicitly[::[Int] <:< List[Int]] // ok!

пока не существует равно-1052 * отношения

implicitly[::[Int] =:= List[Int]] // error!

Однако, на мой взгляд, эти отношения подтипов это не точка алгебры c, тип данных является случайным в Scala. Вместо этого отношения между конструктором ::[T] и типом List[T], значения которого он создает, более аналогичны отношениям между функцией и типом .

Является ли new ::[Int](4, Nil) таким же, как List[Int](4)?

Они оба оценивают одно и то же значение, например, следующие проходы

assert(new ::[Int](4, Nil) == List[Int](4))

однако способ, которым мы достигаем этого значения, совершенно другой. В первом случае мы используем непосредственно конструктор List[Int] ADT, тогда как во втором случае мы фактически вызываем метод apply для сопутствующего объекта trait List[T]

List.apply[Int](4)

, где apply определяется как

def apply[A](xs: A*): List[A] = xs.toList

Теперь на практике нам редко приходится думать об этих вещах. Обычно мы просто пишем что-то вроде

List(4, 2, 42)

и покончим с этим. «Низкоуровневый» конструктор :: иногда показывает свое лицо во время сопоставления с шаблоном , что является способом деструктурирования ADT, например, вот рекурсивная реализация, которая получает сумму элементов в список

def sum(l: List[Int]): Int = {
  l match {
    case Nil => 0
    case head :: tail => head + sum(tail)
  }
}

sum(List(1, 2, 42)) 
// res1: Int = 45
1 голос
/ 17 января 2020

abstract class List запечатан, поэтому он расширяется только классом ::[+A], а object Nil.

. Это означает, что для соответствия type List[+A] объект должно быть type ::[X], где X соответствует A или Nil, что соответствует List[Nothing].

Значение type List[Int] вполне может быть Nil, поскольку List[Nothing] соответствует List[Int]. С другой стороны, вы не можете присвоить Nil значению type ::[Int], поскольку это не ::[_].

TL; DR: каждый ::[_] является List[_], но не каждый List[_] является ::[_].

...