2012-11-28 15 views
5

Ich versuche, einen neuen Typ, Chunk, der eine Karte ähnelt zu implementieren. Grundsätzlich ist ein "Chunk" entweder ein Mapping von String -> Chunk oder eine Zeichenkette selbst.scala benutzerdefinierte Karte

ZB sollte es in der Lage sein, wie dies funktioniert:

val m = new Chunk("some sort of value") // value chunk 
assert(m.getValue == "some sort of value") 

val n = new Chunk("key" -> new Chunk("value"), // nested chunks 
        "key2" -> new Chunk("value2")) 
assert(n("key").getValue == "value") 
assert(n("key2").getValue == "value2") 

Ich habe dies meistens arbeiten, mit der Ausnahme, dass ich bin ein wenig verwirrt durch, wie der Operator + für unveränderliche Karten funktioniert.

Hier ist, was ich habe jetzt:

class Chunk(_map: Map[String, Chunk], _value: Option[String]) extends Map[String, Chunk] { 
    def this(items: (String, Chunk)*) = this(items.toMap, None) 
    def this(k: String) = this(new HashMap[String, Chunk], Option(k)) 
    def this(m: Map[String, Chunk]) = this(m, None) 

    def +[B1 >: Chunk](kv: (String, B1)) = throw new Exception(":(do not know how to make this work") 
    def -(k: String) = new Chunk(_map - k, _value) 
    def get(k: String) = _map.get(k) 
    def iterator = _map.iterator 

    def getValue = _value.get 
    def hasValue = _value.isDefined 

    override def toString() = { 
    if (hasValue) getValue 
    else "Chunk(" + (for ((k, v) <- this) yield k + " -> " + v.toString).mkString(", ") + ")" 
    } 

    def serialize: String = { 
    if (hasValue) getValue 
    else "{" + (for ((k, v) <- this) yield k + "=" + v.serialize).mkString("|") + "}" 
    } 
} 

object main extends App { 
    val m = new Chunk("message_info" -> new Chunk("message_type" -> new Chunk("boom"))) 
    val n = m + ("c" -> new Chunk("boom2")) 
} 

Auch Kommentare, ob im allgemeinen diese Implementierung würde geschätzt angemessen ist.

Danke!

Edit: Die algebraische Datentypen Lösung ist ausgezeichnet, aber es bleibt ein Problem.

def +[B1 >: Chunk](kv: (String, B1)) = Chunk(m + kv) // compiler hates this 
def -(k: String) = Chunk(m - k) // compiler is pretty satisfied with this 

Der Operator - hier scheint zu funktionieren, aber der Operator + will, dass ich wirklich etwas vom Typ B1 zurückzukehren (glaube ich)? Es schlägt mit dem folgenden Problem:

overloaded method value apply with alternatives: (map: Map[String,Chunk])MapChunk <and> (elems: (String, Chunk)*)MapChunk cannot be applied to (scala.collection.immutable.Map[String,B1]) 

Edit2: Xiefei diese Frage beantwortet - Karte erstreckt erfordert, dass ich Griff + mit einem Supertyp (B1) von Chunk, so um dies zu tun, ich haben müssen einig Implementierung für das, so wird dies genügen:

def +[B1 >: Chunk](kv: (String, B1)) = m + kv 

aber ich stattdessen nicht immer wirklich die Absicht, dass man zu verwenden, werde ich auch ist meine Implementierung, die ein Stück zurück, wie folgt:

def +(kv: (String, Chunk)):Chunk = Chunk(m + kv) 
+0

Die Implementierung für '+', im Falle von unveränderlichen Maps, muss eine neue Map mit dem hinzugefügten Schlüssel/Wert zurückgeben, richtig? Sie "wickeln" bereits eine Map ('_map'), so dass für eine Implementierung von' + 'nur das Delegieren zu' _map' funktioniert: 'def + [B1>: Chunk] (kv: (String, B1)) = _map + kv'. Das eigentliche Problem ist, dass dies für "einfache" Chunk's (die nur ein String sind) keinen Sinn macht. Mit anderen Worten, wenn Sie die oben beschriebenen Konstruktoren haben, bricht ** und ** das Implementieren von '+' Ihre Definition eines 'Chunk' ... ab, weil es einem String-Chunk erlaubt, // auch // Map zu werden- Stück. – Faiz

Antwort

1

Haben Sie überlegt, Komposition statt Vererbung zu verwenden? Anstatt also Chunk direkt die Map [String, Chunk] zu erweitern, muss Chunk intern eine Instanz von Map [String, Chunk] enthalten und die zusätzlichen Methoden bereitstellen, die Sie benötigen, und andernfalls an die Methoden der internen Map delegieren.

+1

Ich habe darüber nachgedacht, und es ist ein ziemlich guter Punkt, ich war jedoch versucht, von all den Leckereien, die kostenlos enthalten sind, durch die Karte zu erweitern. – mattomatic

1
def +(kv: (String, Chunk)):Chunk = new Chunk(_map + kv, _value) 
override def +[B1 >: Chunk](kv: (String, B1)) = _map + kv 

Was Sie brauchen, ist eine neue + Methode, und auch die eine in Map Charakterzug erklärt implementieren.

2

Wie es geschrieben ist, gibt es keine Möglichkeit zu erzwingen, dass es nicht gleichzeitig Map und String sein kann. Ich würde den Wert suche mit Either zu erfassen und dem Hinzufügen von was auch immer Bequemlichkeit Methoden, die Sie benötigen:

case class Chunk(value:Either[Map[String,Chunk],String]) { 
    ... 
} 

Das wird man auch darüber nachdenken, zwingen, was Sie wirklich in Situationen tun müssen, wie ein Schlüssel/Wert-Paar Hinzufügen zu a Chunk, das eine String darstellt.

5

Wie wäre es mit einem Algebraic data type Ansatz?

abstract sealed class Chunk 
    case class MChunk(elems: (String, Chunk)*) extends Chunk with Map[String,Chunk] { 
    val m = Map[String, Chunk](elems:_*) 
    def +[B1 >: Chunk](kv: (String, B1)) = m + kv 
    def -(k: String) = m - k 
    def iterator = m.iterator 
    def get(s: String) = m.get(s) 
    } 
    case class SChunk(s: String) extends Chunk 
    // A 'Companion' object that provides 'constructors' and extractors.. 
    object Chunk { 
    def apply(s: String) = SChunk(s) 
    def apply(elems: (String, Chunk)*) = MChunk(elems: _*) 
    // just a couple of ideas... 
    def unapply(sc: SChunk) = Option(sc).map(_.value) 
    def unapply(smc: (String, MChunk)) = smc match { 
     case (s, mc) => mc.get(s) 
    } 

    } 

mit dem Sie wie:

val simpleChunk = Chunk("a") 
val nestedChunk = Chunk("b" -> Chunk("B")) 
// Use extractors to get the values. 
val Chunk(s) = simpleChunk // s will be the String "a" 
val Chunk(c) = ("b" -> nestedChunk) // c will be a Chunk: Chunk("B") 
val Chunk(c) = ("x" -> nestedChunk) // will throw a match error, because there's no "x" 
// pattern matching: 
("x" -> mc) match { 
    case Chunk(w) => Some(w) 
    case _ => None 
} 

Die unapply Extraktoren nur ein Vorschlag sind; Hoffentlich kannst du dich mit dieser Idee anlegen, bis du bekommst, was du willst.

+2

+1 Die Formulierung * "Im Grunde ist ein Chunk entweder ein Mapping von String -> Chunk, oder eine Zeichenkette selbst." * Imho fordert eine Art versiegelte abstrakte Klasse/Eigenschaft. Die offene Frage an mich ist, welche Art von Funktionalität in dieser Superklasse enthalten sein sollte. Sollte es sich im Allgemeinen wie eine Karte oder ein String verhalten? In dieser Hinsicht ist mir die Frage nicht völlig klar ... – bluenote10