2010-11-19 10 views
5

Estoy tratando de implementar un default valued map, y me gustaría filtros, mapas, etc. durante un DefaultingMap que también producen una DefaultingMap siempre que sea posible. Aquí está mi aplicación inicial:Implementar una colección Scala de manera que un mapa, filtro, etc. producir el tipo correcto

class DefaultingMap[K, V](defaultValue: => V) 
extends mutable.HashMap[K, V] 
with mutable.MapLike[K, V, DefaultingMap[K, V]] { 

    override def empty = new DefaultingMap[K, V](defaultValue) 

    override def default(key: K): V = {     
    val result = this.defaultValue 
    this(key) = result 
    result            
    } 
} 

consigo objetos de tipo DefaultingMap cuando uso filter, pero no cuando se utiliza map:

scala> val counter = new DefaultingMap[Char, Int](0) 
counter: DefaultingMap[Char,Int] = Map() 

scala> for (c <- "ababcbbb") counter(c) += 1 

scala> counter.filter{case (k, v) => v > 1} 
res1: DefaultingMap[Char,Int] = Map((a,2), (b,5)) 

scala> counter.map{case (k, v) => (k, v * 2)} 
res2: scala.collection.mutable.HashMap[Char,Int] = Map((a,4), (c,2), (b,10)) 

La diferencia entre estos dos métodos parece ser que map toma una implícito CanBuildFrom. Así que deduzco que necesito tener un implicit def en algún lugar para proporcionar el CanBuildFrom. Mi primera intuición era hacer lo que se hace en HashMap:

object DefaultingMap extends generic.MutableMapFactory[DefaultingMap] { 

    def empty[K, V]: DefaultingMap[K, V] = // Not possible! 

    implicit def canBuildFrom[K, V]: 
    generic.CanBuildFrom[Coll, (K, V), DefaultingMap[K, V]] = 
     new MapCanBuildFrom[K, V] 
} 

Creo que esto sería que se compile, pero este método no funcionará porque es imposible definir el método empty - lo que necesita saber lo que el defaultValue debiera ser. Si pudiera definir el CanBuildFrom en la clase misma, en lugar del objeto complementario, estaría bien porque el defaultValue está disponible allí.

¿Cómo puedo hacer que funcione?

Respuesta

5

mapas son mutables Builder s en Scala, por lo que el MapFactory por defecto tiene un mapa vacío del tipo en cuestión para obtener un constructor.

Si tiene reglas de compilación de mapas personalizadas, una de las cosas que puede hacer es definir su fábrica personalizada similar a collection.generic.MapFactory. Tendría que definirlo de manera similar a como lo hace allí, pero haga que tanto el método empty como el método newBuilder tomen un argumento adicional para el defaultValue.

Algo a lo largo de las líneas (si se lee más sobre el API Scala 2,8 colecciones en el otro enlace sugerido, usted encontrará que usted no tiene que poner en práctica compañero genérica para los mapas):

import collection._                  


class DefaultingMap[K, V](val defaultValue: V)                  
extends mutable.HashMap[K, V]                      
with mutable.MapLike[K, V, DefaultingMap[K, V]] {                 

    override def empty = new DefaultingMap(defaultValue)                

}                             


object DefaultingMap {                        
    def newBuilder[K, V](d: V): DefaultingMap[K, V] = new DefaultingMap[K, V](d)          

    implicit def canBuildFrom[K, V] =                     
    new generic.CanBuildFrom[DefaultingMap[K, V], (K, V), DefaultingMap[K, V]] {          
     def apply(from: DefaultingMap[K, V]) = newBuilder[K, V](from.defaultValue)          
     def apply() = error("unsupported default apply")                
    }                            
}                             


object Main {                          
    def main(args: Array[String]) {                     
    println((new DefaultingMap[Int, Int](5)).defaultValue)               
    println(((new DefaultingMap[Int, Int](5)).map(x => x)).defaultValue)            
    }                             
} 

Lienzo:

$ scalac defaulting.scala 
$ scala Main 
5 
5 

admito, aún así, esto no resuelve el problema para el apply sin parámetros.

+0

El punto de que MutableMaps ya es Builders es bueno, eso simplifica el código en algunos lugares. En cuanto a la aplicación sin parámetros, ¿sabes cuándo se invoca? Quizás si no es común no estoy demasiado preocupado por eso. – Steve

+0

Por cierto, creo que podría ser más claro si se descarta el método 'newBuilder' y acaba de crear el DefaultingMap directamente. – Steve

+0

Parameterless 'apply' se invoca en' breakOut', que es el único lugar, que yo sepa ... Vea el objeto del paquete de recopilación. Y tienes razón sobre 'newBuilder'. Sin embargo, aún dejaría la definición del método 'newBuilder' en el acompañante. Si el 'Mapa' tuviera compañeros que funcionaran de la misma manera que' Seq's, entonces sería útil. Además, los haría compatibles con los compañeros estándar de 'MapFactory' que tienen el' newBuilder'. – axel22

0

El Scala 2.8 Collections API es un documento muy agradable y me parece recordar que la discusión de este aspecto de la transformación, aunque no recuerdo exactamente dónde. Supongo que no es muy útil ...

+3

No, no tanto. ;-) – Steve

2

Si utiliza collection.immutable.Map en 2,8 o superior, entonces el método withDefault está disponible para usted:

val m = collection.immutable.Map(1->"a", 2->"b", 3->"c") 
val n = m withDefaultValue "default" 

// n(7) will return "default" 

ACTUALIZACIÓN

Si' re mapeo sobre la colección, mueva el withDefaultValue hasta el final de la cadena de procesamiento:

val o = (for ((k, v) <- m) yield (k, v)) withDefaultValue "default" 
// o(0) will return "default" 
+0

El método 'withDefaultValue' tampoco funciona con' map'. Pruebe 'val o = for ((k, v) <- n) yield (k, v); o (0) 'después del código anterior y obtendrás' NoSuchElementException'. – Steve

+0

@Steve De acuerdo con su comentario (ahora eliminado), no es necesario que sea inmutable. Intente hacer el recuento inicial de esta manera: '" ababcbbb ".groupBy (identity) mapValues ​​{_.size}' –

+0

Poner el 'withDefaultValue' al final de la cadena de procesamiento no es realmente una buena opción. Puede que no sepa dónde está el final de la cadena de procesamiento, particularmente si devuelvo este objeto a otra persona. No sabré cómo es su cadena de procesamiento. – Steve

1

Ok, aquí es un enfoque que es muy, muy cerca de lo que quiero:

class DefaultingMap[K, V](defaultValue: => V) 
extends mutable.HashMap[K, V] 
with mutable.MapLike[K, V, DefaultingMap[K, V]] { 

    override def empty = new DefaultingMap[K, V](defaultValue) 

    override def default(key: K): V = {     
    val result = this.defaultValue 
    this(key) = result 
    result            
    } 

    implicit def canBuildFrom[NK] = 
    new generic.CanBuildFrom[DefaultingMap[K, V], (NK, V), DefaultingMap[NK, V]] { 
     def apply(from: DefaultingMap[K, V]) = 
     new DefaultingMap[NK, V](from.newDefaultValue) 
     def apply() = 
     new DefaultingMap[NK, V](defaultValue) 
    } 

    def newDefaultValue = defaultValue 
} 

Ahora, si consigo que CanBuildFrom en alcance, todo funciona como un encanto:

scala> val counter = new DefaultingMap[Char, Int](0) 
counter: DefaultingMap[Char,Int] = Map() 

scala> for (c <- "ababcbbb") counter(c) += 1 

scala> import counter._ 
import counter._ 

scala> counter.map{case (k, v) => (k, v * 2)} 
res1: DefaultingMap[Char,Int] = Map((a,4), (c,2), (b,10)) 

scala> for ((k, v) <- counter; if v > 1) yield (k.toString, v * 2) 
res2: DefaultingMap[java.lang.String,Int] = Map((a,4), (b,10)) 

Sin embargo, si dejo fuera de ese import counter._, obtengo el mismo comportamiento que antes. Si puedo encontrar la manera de encontrar ese implicit def canBuildFrom, me estableceré ...

+0

Tenga en cuenta que esto se degrada al hacer 'counter.map {case (k, v) => (k, v.toString)}'. Ahora el valor predeterminado es de tipo 'Any'. – Debilski

+0

Ese es el tipo inferido correctamente: tienes elementos 'String' pero un valor predeterminado' Int' y lo único que comparten en común es 'Any'. Pero creo que probablemente tengas razón de que sería más útil simplemente soltar 'NV>: V' a favor de' V' solo para que en este caso obtengas un 'HashMap [Char, String] 'en este caso. – Steve

+0

Ok, lo edité como se sugirió: ya no es 'canBuildFrom [NK, NV>: V]' sino simplemente 'canBuildFrom [NK]'. – Steve

0

No le ayudará con el tipo de devolución de map (pero de nuevo, el valor predeterminado del nuevo la recopilación generalmente no se puede definir utilizando la transformación map). Pero para darle otro enfoque:

class DefaultHashMap[A, B](dflt: => B) extends scala.collection.mutable.Map[A, B] { 
    val underlying = new scala.collection.mutable.HashMap[A, B]() 

    def get(key: A) = underlying.get(key) orElse Some(default(key)) 
    def iterator: Iterator[(A, B)] = underlying.iterator 
    def +=(kv: (A, B)) = { underlying += kv; this } 
    def -=(key: A) = { underlying -= key; this } 
    override def empty: DefaultHashMap[A, B] = new DefaultHashMap[A, B](dflt) 
    override def default(key: A) = dflt 
} 
+1

Sí, desafortunadamente, el tipo de devolución del 'mapa' es casi la clave del problema. Necesito esos valores predeterminados para quedarse. – Steve

Cuestiones relacionadas