2013-03-23 1 views
7

Ich versuche gerade, Scala mit der Absicht, es für mein nächstes Projekt, das sich mit DICOM befassen muss, einzuhüllen. DICOM hat eine ziemlich breite Spezifikation, die sich über Tausende von Seiten des Standards erstreckt. Mein Verständnis von DICOM ist ziemlich begrenzt, aber kurz gesagt, DICOM-Objekte - IODs (Information Object Definition) bestehen aus Modulen, und Module sind eine Sammlung typisierter Name-Wert-Attributpaare. Es wird durch die Optionalität einiger Module und Attribute noch komplizierter. Zum Beispiel:Scala - Domain Object Modeling

SimpleImageIOD: { 
    PatientModule: { 
     name: String 
     dateOfBirth: DateTime 
    } 
    StudyModule: { 
     name: String 
     date: DateTime (optional) 
    } 
    SeriesModule: { 
     name: String 
    } 
    ImageModule: { 
     height: Integer 
     width: Integer 
     pixelSize: Double (optional) 
    } 
    EquipmentModule: { (optional) 
     type: String 
    } 
} 

Es gibt Unmengen von Modulen, die in verschiedenen Kombinationen zusammengesetzt sein können und unterschiedliche IODs bilden. Scala hat wiederum viel Modellierungskraft mit allen Eigenschaften, Fallklassen, dynamischen Klassen usw. Wie würden Sie eine solche Domäne in Scala modellieren? Ich bin ganz neu in der Sprache, aber ich habe nachgedacht unveränderlichen Fall Klassen verwenden Module zu definieren, aggregieren sie dann in verschiedenen IOD, und verwenden Linsen für die Aktualisierung:

case class Patient(name: String, dateOfBirth: DateTime) 
case class Study(name: String, date: Option[DateTime]) 
case class Series(name: String) 
case class Image(height: Integer, width: Integer, pixelSize: Option[Double]) 
case class Equipment(type: String) 

case class SimpleImageIOD(patient: Patient, study: Study, series: Series, 
          image: Image, equipment: Option[Equipment]) 

object Patient { 
    val nameL: Lens[Patient, String] = ... 
    val dateOfBirthL: Lens[Patient, DateTime] = ... 
} 

object SimpleImageIOD { 
    val patientL: Lens[SimpleImageIOD, Patient] = ... 
    val patientNamel = patientL andThen Patient.nameL 
} 

usw. usw. In Bezug auf Linsen es könnte ein Problem werden, alle Boilerplate zu codieren - es wird eine Reihenfolge der M x N x L Objektive geben, um die gesamte Domäne für M IODs, N Module und L Attribute abzudecken. Auch die Optionalität einiger Felder erschwert die Aufgabe immens zumindest mit scalaz-seven.

Welche anderen realisierbaren Ansätze gibt es dort, die mit dem Geist von Scala übereinstimmen?

Antwort

5

Sie können Objektive verschachteln, also glaube ich nicht, dass Sie insgesamt M x N x L Objektive haben werden. Ich denke, das ist ein vollkommen vernünftiger Ansatz.

Ein alternativer Ansatz ist Ihre eigenen einströmige Aktualisierungsmethoden wie so zu definieren:

case class Patient(name: String, dateOfBirth: Long) { 
    def name(f: String => String): Patient = copy(name = f(name)) 
    def dateOfBirth(f: Long => Long): Patient = copy(dateOfBirth = f(dateOfBirth)) 
} 

, die Sie wie so verwenden:

scala> Patient("John",0).name(_ + " Smith") 
res1: Patient = Patient(John Smith,0) 

Dieses mit Linsen Art erreichbar ist, aber Sie muss sehr vorsichtig sein, um drastisch mehr Boilerplate zu vermeiden (sowohl in der Verwendung als auch in der Definition). Es hat nicht die Flexibilität von Objektiven, bei denen man Fähigkeiten willkürlich auseinander ziehen kann (z. B. Aktualisieren), aber es macht es dadurch wieder wett, indem es gestrafft wird.

ich die Mehrheit meiner tiefen Aktualisierung auf diese Weise:

myImage.patient(_.study(_.date(_ => Some(System.currentTimeMillis)))) 

ist alles, was Sie den gesamten Baum mit einer Studie mit der aktuellen Zeit zu aktualisieren (vorgibt hier, dass DateTime ist eigentlich ein Long) und wenn Sie wirklich brauchen, um diese Fähigkeit zu extrahieren, diese paticular Baum Regenerieren (die mit Linsen mehr eines natürlichen ist), könnten Sie

val setStudyTime = (t: Long) => myImage.patient(_.study(_.date(_ => Some(t)))) 

oder sogar

def studyTimeSetter(si: SimpleImageIOD) = 
    (t: Long) => si.patient(_.study(_.date(_ => Some(t)))) 

Wenn Sie diese Fähigkeit leicht sofort erfassen müssen.

Wenn Sie alles brauchen, was Objektive Ihnen bieten, ist es mehr Arbeit, es auf eine Ad-hoc-Weise mit diesem Ansatz neu zu erstellen, aber wenn Sie nur einen kleinen Bruchteil benötigen und meistens nur auf dem Vorsatz für die Einstellung schneiden müssen macht einen ziemlich guten Job.

+0

Was meinst du mit "Linsen verschachteln"? –

+0

@ak. - Nun, "komponiere" wirklich. Wenn Sie etwas wie "Long => Patient" und einen "Patient => SimpleImageIOD" haben, können Sie sie zusammenstellen, um ein 'Long => SimpleImageIOD' zu erhalten. Sie müssen nicht jede Möglichkeit aufbauen; Sie können es tun, wo Sie sie verwenden. –

+0

In Bezug auf Scalaz-Objektive meinst du wahrscheinlich 'andThen' anstatt' compose'. Ich denke, 'compose' angewendet auf zwei Linsen' Long => Patient' und 'String => Patient' wird eine Linse produzieren, die' (Long, String) => Patient'. Aber der Punkt ist nicht, Linsen auf dem Boilerplate zu verschachteln, nicht wahr? –