Concatenación de lista Scala, ::: vs ++

¿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 

De la documentación parece que ++ es más general, mientras que ::: es List -específico. ¿Se proporciona este último porque se usa en otros lenguajes funcionales?

Legado. La lista se definió originalmente para ser funcional-lenguajes que buscan:

 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 del código y una API consistente, por lo que puede usar ++ para concatenar dos colecciones, e incluso iteradores. List, sin embargo, tiene que mantener sus operadores originales, aparte de uno o dos que se desaprobaron.

::: funciona solo con listas, mientras que ++ se puede usar con cualquier trasabilidad. En la implementación actual (2.9.0), ++ recurre a ::: si el argumento es también una List .

Siempre usa ::: . Hay dos razones: eficiencia y tipo de seguridad.

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 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 de mezclar 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) 

Entonces, si está usando macros, debe tener cuidado.

Además, ++ para dos listas llama a ::: pero con más sobrecarga porque está pidiendo un valor implícito para tener un constructor de Lista a Lista. Pero las microbenchmarks no probaron nada útil en ese sentido, supongo que el comstackdor optimiza esas llamadas.

Micro-Benchmarks después del calentamiento.

 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.