generics - Scala 2.8 CanBuildFrom
scala-2.8 (2)
A continuación de otra pregunta que hice, Scala 2.8 breakout , quería entender un poco más sobre el método de Scala TraversableLike[A].map
cuya firma es la siguiente:
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
Observe algunas cosas sobre este método:
- Toma una función que convierte cada
A
en el trasable enB
- Devuelve
That
y toma un argumento implícito de tipoCanBuildFrom[Repr, B, That]
.
Puedo llamar esto de la siguiente manera:
> val s: Set[Int] = List("Paris", "London").map(_.length)
s: Set[Int] Set(5,6)
Lo que no puedo entender es cómo el compilador está haciendo cumplir el hecho de que That
está obligado a B
(es decir, es una colección de B). Los parámetros de tipo parecen ser independientes tanto de la firma anterior como de la firma del rasgo CanBuildFrom
:
trait CanBuildFrom[-From, -Elem, +To]
¿Cómo se asegura el compilador de Scala que That
no puede forzarse en algo que no tiene sentido?
> val s: Set[String] = List("Paris", "London").map(_.length) //will not compile
¿Cómo decide el compilador qué objetos CanBuildFrom
implícitos están en el alcance de la llamada?
Tenga en cuenta que el segundo argumento para map
es un argumento implícito. Debe haber un alcance implícito con los tipos apropiados, o, de lo contrario, debe pasar tal argumento.
En su ejemplo, debe ser Set[String]
, B debe ser Int
y Repr
debe ser List[String]
. Por lo tanto, para que compile necesita el siguiente objeto implícito en el alcance:
implicit object X: CanBuildFrom[List[String], Int, Set[String]]
No hay tal cosa en el alcance. Además, breakOut
no puede proporcionarlo, ya que, en sí mismo, necesita un CanBuildFrom
implícito, cuyo primer tipo puede ser cualquier clase (un descendiente contra-variante de Nothing
), pero de otro modo restringido por los otros tipos.
Eche un vistazo, por ejemplo, en la fábrica de CanBuildFrom
desde el objeto complementario de List
:
implicit def canBuildFrom [A] : CanBuildFrom[List, A, List[A]]
Debido a que enlaza los parámetros segundo y tercero a través de A
, lo implícito en cuestión no funcionará.
Entonces, ¿cómo sabe uno dónde buscar con respecto a tales implicaciones? En primer lugar, Scala sí importa algunas cosas en todos los ámbitos. En este momento, puedo recordar las siguientes importaciones:
import scala.package._ // Package object
import scala.Predef._ // Object
// import scala.LowPriorityImplicits, class inherited by Predef
import scala.runtime._ // Package
Como estamos preocupados por las implicaciones, tenga en cuenta que cuando importa cosas desde paquetes, las únicas implicaciones posibles son los singletons. Cuando importa cosas desde objetos (singletons), por otro lado, puede tener definiciones implícitas, valores y singletons.
En este momento, hay CanBuildFrom
dentro de LowPriorityImplicits
y LowPriorityImplicits
, que están relacionados con las cadenas. Nos permiten escribir el "this is a string" map (_.toInt)
.
Entonces, salvo estas importaciones automáticas, y las importaciones explícitas que realiza, ¿dónde más se puede encontrar un implícito? Un lugar: los objetos complementarios de la instancia en la que se aplica el método.
Digo los objetos complementarios s , en plural, porque los objetos complementarios de todos los rasgos y clases heredados por la clase de la instancia en cuestión pueden contener implícitos relevantes. No estoy seguro de si la instancia en sí puede contener un implícito. Para ser sincero, no puedo reproducir esto ahora mismo, así que ciertamente estoy cometiendo un error de algún tipo aquí.
En cualquier caso, mira dentro de los objetos complementarios.
object ArrayBuffer extends SeqFactory[ArrayBuffer] {
/** $genericCanBuildFromInfo */
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, ArrayBuffer[A]] = new GenericCanBuildFrom[A]
def newBuilder[A]: Builder[A, ArrayBuffer[A]] = new ArrayBuffer[A]
}