create - fill list scala
Scala list concatenation,::: vs++ (4)
¿Hay alguna diferencia entre :::
y ++
para concatenar listas en 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
En la documentación parece que ++
es más general mientras que :::
es List
-specific. ¿Se proporciona este último porque se usa en otros lenguajes funcionales?
Legado. La lista se definió originalmente para que fuera de un lenguaje funcional:
1 :: 2 :: Nil // a list
list1 ::: list2 // concatenation of two lists
list match {
case head :: tail => "non-empty"
case Nil => "empty"
}
Por supuesto, Scala desarrolló otras colecciones, de una manera ad hoc. Cuando salió 2.8, las colecciones se rediseñaron para una máxima reutilización de código y una API consistente, de modo que pueda usar ++
para concatenar dos colecciones, e incluso iteradores. Lista, sin embargo, consiguió conservar sus operadores originales, aparte de uno o dos que quedaron en desuso.
Siempre use :::
. Hay dos razones: eficiencia y seguridad de tipo.
Eficiencia
x ::: y ::: z
es más rápido que x ++ y ++ z
, porque :::
es asociativo correcto. x ::: y ::: z
se analiza como x ::: (y ::: z)
, que es algorítmicamente más rápido que (x ::: y) ::: z
(este último requiere O (| x |) más pasos).
Tipo de seguridad
Con :::
solo puedes concatenar dos List
s. Con ++
puedes agregar cualquier colección a la List
, lo cual es terrible:
scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)
++
también es fácil mezclarse con +
:
scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab
Un punto diferente es que la primera oración se analiza como:
scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)
Mientras que el segundo ejemplo se analiza como:
scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)
Así que si estás usando macros, debes tener cuidado.
Además, ++
para dos listas está llamando a :::
pero con más sobrecarga porque solicita un valor implícito para tener un constructor de Lista a Lista. Pero los microbenchmarks no demostraron nada útil en ese sentido, supongo que el compilador optimiza tales llamadas.
Micro-Benchmarks después de calentar.
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
Como dijo Daniel C. Sobrai, puede agregar el contenido de cualquier colección a una lista usando ++
, mientras que con :::
solo puede concatenar listas.
:::
funciona solo con listas, mientras que ++
se puede usar con cualquier atravesable. En la implementación actual (2.9.0), ++
recurre a :::
si el argumento también es una List
.