2010-06-21 6 views
9

Ich möchte eine Klasse C zum Speichern von Werten verschiedener numerischer Typen sowie Boolean implementieren. Darüber hinaus möchte ich in der Lage sein, Instanzen dieser Klasse zu betreiben, zwischen den Typen zu konvertieren Int --> Double und Boolean -> Int, dh Boolean + Boolean, Int + Boolean, Boolean + Int, Double + Double usw. zu addieren und das kleinstmögliche zurückzugeben Geben Sie (Int oder Double) wann immer möglich ein.Wie wird eine implizite Konvertierung eingerichtet, um Arithmetik zwischen numerischen Typen zu ermöglichen?

Bisher kam ich mit auf dem Punkt:

abstract class SemiGroup[A] { def add(x:A, y:A):A } 

class C[A] (val n:A) (implicit val s:SemiGroup[A]) { 
    def +[T <% A](that:C[T]) = s.add(this.n, that.n) 
} 

object Test extends Application { 
    implicit object IntSemiGroup extends SemiGroup[Int] { 
    def add(x: Int, y: Int):Int = x + y 
    } 

    implicit object DoubleSemiGroup extends SemiGroup[Double] { 
    def add(x: Double, y: Double):Double = x + y 
    } 

    implicit object BooleanSemiGroup extends SemiGroup[Boolean] { 
    def add(x: Boolean, y: Boolean):Boolean = true; 
    } 

    implicit def bool2int(b:Boolean):Int = if(b) 1 else 0 

    val n = new C[Int](10) 
    val d = new C[Double](10.5) 
    val b = new C[Boolean](true) 

    println(d + n) // [1] 
    println(n + n) // [2] 
    println(n + b) // [3] 
    // println(n + d) [4] XXX - no implicit conversion of Double to Int exists 
    // println(b + n) [5] XXX - no implicit conversion of Int to Boolean exists 
} 

Dies ist für manche Fälle funktioniert (1, 2, 3), aber nicht für (4, 5). Der Grund ist, dass es eine implizite Erweiterung des Typs von niedriger auf höher gibt, aber nicht umgekehrt. In gewisser Weise das Verfahren

def +[T <% A](that:C[T]) = s.add(this.n, that.n) 

irgendwie einen Partner Methode haben muss, die etwas aussehen würde:

def +[T, A <% T](that:C[T]):T = that.s.add(this.n, that.n) 

aber das aus zwei Gründen nicht kompilieren, erstens, dass der Compiler nicht this.n umwandeln kann Geben Sie T ein (auch wenn wir die Sichtgrenze angeben A <% T), und zweitens, selbst wenn es in der Lage wäre, this.n zu konvertieren, werden die beiden Methoden + mehrdeutig.

Sorry, das ist so lang. Jede Hilfe würde sehr geschätzt werden! Ansonsten scheint es, dass ich alle Operationen explizit zwischen allen Typen schreiben muss. Und es würde haarig werden, wenn ich zusätzliche Typen hinzufügen müsste (Complex steht als nächstes auf der Speisekarte ...).

Vielleicht hat jemand einen anderen Weg, um das alles zusammen zu erreichen? Fühlt sich an, als gäbe es etwas Einfaches, das ich übersehe.

Vielen Dank im Voraus!

Antwort

6

Okay, dann Daniel!

Ich habe die Lösung beschränkt, Boolean zu ignorieren, und nur mit AnyVals arbeiten, die eine schwache Least Upper Bound, die eine Instanz von Numeric hat. Diese Einschränkungen sind willkürlich, Sie könnten sie entfernen und Ihre eigene schwache Konformitätsbeziehung zwischen Typen kodieren - die Implementierung von a2b und a2c könnte einige Konvertierungen durchführen.

Seine interessant, wie implizite Parameter zu berücksichtigen Vererbung simulieren kann (implizite Parameter vom Typ vorbei (Abgeleitet => Basis) oder Weak Conformance. Sie sind wirklich stark, vor allem, wenn der Typ Rückschließer hilft Ihnen aus.

Erstens Wir benötigen eine Typenklasse, um die Weak Least Upper Bound aller Typenpaare A und B darzustellen, an denen wir interessiert sind.

sealed trait WeakConformance[A <: AnyVal, B <: AnyVal, C] { 
    implicit def aToC(a: A): C 

    implicit def bToC(b: B): C 
} 

object WeakConformance { 
    implicit def SameSame[T <: AnyVal]: WeakConformance[T, T, T] = new WeakConformance[T, T, T] { 
    implicit def aToC(a: T): T = a 

    implicit def bToC(b: T): T = b 
    } 

    implicit def IntDouble: WeakConformance[Int, Double, Double] = new WeakConformance[Int, Double, Double] { 
    implicit def aToC(a: Int) = a 

    implicit def bToC(b: Double) = b 
    } 

    implicit def DoubleInt: WeakConformance[Double, Int, Double] = new WeakConformance[Double, Int, Double] { 
    implicit def aToC(a: Double) = a 

    implicit def bToC(b: Int) = b 
    } 

    // More instances go here! 


    def unify[A <: AnyVal, B <: AnyVal, C](a: A, b: B)(implicit ev: WeakConformance[A, B, C]): (C, C) = { 
    import ev._ 
    (a: C, b: C) 
    } 
} 

Verfahren unify kehrt C Art, die durch die Art Rückschließer nach Verfügbarkeit von impliziten Werten basiert heraus ev wird als implizite Argument zu liefern.

Wir können dies in Ihre Wrapper-Klasse C wie folgt einstecken, wobei wir auch Numeric[WeakLub] benötigen, damit wir die Werte hinzufügen können.

case class C[A <: AnyVal](val value:A) { 
    import WeakConformance.unify 
    def +[B <: AnyVal, WeakLub <: AnyVal](that:C[B])(implicit wc: WeakConformance[A, B, WeakLub], num: Numeric[WeakLub]): C[WeakLub] = { 
    val w = unify(value, that.value) match { case (x, y) => num.plus(x, y)}; 
    new C[WeakLub](w) 
    } 
} 

Und schließlich setzen sie alle zusammen:

object Test extends Application { 
    val n = new C[Int](10) 
    val d = new C[Double](10.5) 

    // The type ascriptions aren't necessary, they are just here to 
    // prove the static type is the Weak LUB of the two sides. 
    println(d + n: C[Double]) // C(20.5) 
    println(n + n: C[Int]) // C(20) 
    println(n + d: C[Double]) // C(20.5) 
} 

Test 
+0

Ahha! Ich sehe, wie das funktioniert - danke! Das Hinzufügen eines 'Boolean' stellte sich in der Tat als einfach heraus und die LUB von' Numeric' wird nicht zu schwierig für 'Complex' zu ändern sein. Ich bin neugierig - Sie scheinen diese Lösung im Ärmel zu haben, in welchem ​​Kontext sind Sie auf dieses Problem gestoßen? Auch ich habe versucht, die Leistung dieser Lösung zu testen und summiert eine Million 'C [Int]' s scheint etwa fünf Mal langsamer als eine Million 'Int's ... Irgendwelche Gedanken darüber, wie ich anfangen soll das optimieren? – ostolop

+0

Ich habe das während einer Diskussion mit @extempore im IRC herumgespielt, es löste kein bestimmtes Problem von mir. 5x Overhead klingt angesichts der Indirektion nicht so schlecht. Sie könnten 'wc.a2b' und' wc.a2c' direkt aufrufen, anstatt die Methode 'unify' zu verwenden. Momentan sind die Ein- und Ausgaben von 'NumeriC# plus' geboxt, hoffentlich wird eine zukünftige Version von Scala einen Weg finden, dieses Problem zu lösen. – retronym

+0

@retronym Eigentlich ... initiierte ich diese Diskussion. :-) –

3

Es gibt eine way das zu tun, aber ich werde es zu retronym erklären, da er diese Lösung geschrieben hat. :-)

Verwandte Themen