Scala конкатенация списка,: vs ++
Есть ли разница между :::
и ++
для объединения списков в Scala?
scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)
scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)
scala> res0 == res1
res2: Boolean = true
Из документации он выглядит как ++
более общий, тогда как :::
является List
-специфичным. Предоставлено ли последнее, потому что оно используется на других функциональных языках?
Ответы
Ответ 1
Наследство. Список изначально был определен как функционально-языки:
1 :: 2 :: Nil // a list
list1 ::: list2 // concatenation of two lists
list match {
case head :: tail => "non-empty"
case Nil => "empty"
}
Разумеется, Scala разработал другие коллекции специальным образом. Когда вышел 2.8, коллекции были переработаны для максимального повторного использования кода и согласованного API, так что вы можете использовать ++
для объединения любых двух коллекций - и даже итераторов. Однако список должен содержать своих исходных операторов, кроме одного или двух, которые устарели.
Ответ 2
:::
работает только со списками, а ++
может использоваться с любым проходящим. В текущей реализации (2.9.0) ++
возвращается на :::
, если аргумент также является List
.
Ответ 3
Всегда используйте :::
. Существует две причины: эффективность и безопасность типов.
Эффективность
x ::: y ::: z
быстрее, чем x ++ y ++ z
, потому что :::
является правильным ассоциативным. x ::: y ::: z
анализируется как x ::: (y ::: z)
, который алгоритмически быстрее, чем (x ::: y) ::: z
(последний требует, чтобы O (| x |) выполнялось больше шагов).
Тип безопасности
С помощью :::
вы можете объединить только два List
s. С помощью ++
вы можете добавить любую коллекцию в List
, что ужасно:
scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)
++
также легко смешивается с +
:
scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab
Ответ 4
Другая точка заключается в том, что первое предложение анализируется как:
scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)
В то время как второй пример анализируется как:
scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)
Итак, если вы используете макросы, вам следует позаботиться.
Кроме того, ++
для двух списков вызывает :::
, но с большим количеством служебных данных, потому что запрашивает неявное значение, чтобы иметь построитель из списка в список. Но микробенчмарки не показали ничего полезного в этом смысле, я думаю, что компилятор оптимизирует такие вызовы.
Micro-Benchmarks после разогрева.
scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46
Как сказал Daniel C. Sobrai, вы можете добавить содержимое любой коллекции в список с помощью ++
, тогда как с помощью :::
вы можете объединять списки.