Numerisch-generisch


Eigentlich bin ich ja schon auf dem Weg zur Silvesterfeier, aber eine Frage im Scala-Forum hat mich dazu gebracht, mir einmal das in Scala 2.8 neu eingeführte generische Trait Numeric[T] anzuschauen. Es steht für alle numerischen Datentypen als implizites Objekt bereit. Benutzt man dieses implizite Objekt für einen gegebenen Datentyp wie Double oder BigInt, kann es einem die Null oder Eins dieses Zahlenbereichs zurückgeben und einige grundlegende Rechenoperationen und Konvertierungen auführen.

Ein naheliegendes Anwendungsbeispiel ist eine numerische Vektor-Klasse. Meine Implementierung benutzt zusätzlich zum Numeric-Trait auch ein ClassManifest, was hier aber nur für die Konstruktion eines Arrays benötigt wird. Eine kleine Besonderheit ist die @specialized-Annotation: Damit erzeugt der Compiler für jede Belegung des annotierten Typ-Parameters mit einem primitiven Typ eine eigene Version der generischen Klasse (so wie es C++ für alle Belegungen eines Templates tut), was natürlich helfen kann, die Performance zu verbessern oder den Speicherbedarf zu senken. Für Details sei auf dieses Papier verwiesen.

//Scala 2.8
class Vector[@specialized("Int,Double") T](ts:T*)(implicit numeric:Numeric[T], implicit m:ClassManifest[T]) {
  private val data = Array(ts: _*) //das geht nur mit ClassManifest

  def apply(index:Int) = data(index)
  def set(index:Int, value:T) = {
    val result = new Vector[T](data: _*)
    result.data(index) = value
    result
  }
  def size = data.size

  def +(that:Vector[T]) =
    new Vector(data.zip(that.data).map{case (a:T,b:T) => numeric.plus(a,b)} : _*)
  def -(that:Vector[T]) =
    new Vector(data.zip(that.data).map{case (a:T,b:T) => numeric.minus(a,b)} : _*)
  def *(that:Vector[T]) =
    new Vector(data.zip(that.data).map{case (a:T,b:T) => numeric.times(a,b)} : _*)
  def unary_- = new Vector(data.map(numeric.negate(_)) : _*)

  override def toString = data.mkString("Vector(",",",")")
}

Auch „alleinstehende“ Methoden können über implizite Objekte Numeric verwenden, z.B. hier eine Methode zum Erzeugen eines Nullvektors:

   def zero[T](size:Int)(implicit num:Numeric[T], m:ClassManifest[T]) = 
       new Vector((0 until size).map(_ => num.zero): _*)

So, das war’s schon. Eigentlich ganz einleuchtend, wenn man sich erst mal an den implicit-Trick gewöhnt hat.

Dann feiert schön, und nochmal einen Guten Rutsch!

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s