2012-05-29 3 views
13

Ich möchte eine Zeichenfolge als eine Instanz einer Fallklasse lesen.Lese Case-Objekt aus String in Scala (etwas wie Haskells "Read" -Typenklasse)

> case class Person(name: String, age: Int) 
> val personString: String = "Person(Bob,42)" 
> val person: Person = read(personString) 

Dies ist das gleiche Verhalten wie die Lesetypeclass in Haskell: Zum Beispiel wurde genannt, wenn die Funktion „lesen“ es würde mich folgendes tun lassen.

+0

Ich finde die einzigen heutigen nützlichen Antworten unten, sind die neueren Beschreibungen von Beiz-Scala-Bibliotheken: [die Scala-Beizbibliothek] (http://stackoverflow.com/a/23610675/1509695) und [uPickle] (http://stackoverflow.com/a/28173416/1509695). – matanster

Antwort

12

dflemstr antwortete mehr auf die Einrichtung der tatsächlichen read-Methode- Ich werde mehr für die tatsächliche Parsing-Methode beantworten.

Mein Ansatz hat zwei Objekte, die in scalas Mustervergleichsblöcken verwendet werden können. AsInt können Sie mit Zeichenfolgen übereinstimmen, die Int s darstellen, und PersonString ist die tatsächliche Implementierung für Person Deserialisierung.

object AsInt { 
    def unapply(s: String) = try{ Some(s.toInt) } catch { 
    case e: NumberFormatException => None 
    } 
} 

val PersonRegex = "Person\\((.*),(\\d+)\\)".r 

object PersonString { 
    def unapply(str: String): Option[Person] = str match { 
    case PersonRegex(name, AsInt(age)) => Some(Person(name, age)) 
    case _ => None 
    } 
} 

Die Magie ist in der unapply Methode, die für Syntax scala Zucker hat. So mit dem PersonString Objekt, könnten Sie

val person = PersonString.unapply("Person(Bob,42)") 
// person will be Some(Person("Bob", 42)) 

zu tun, oder Sie können ein Pattern-Matching-Block zu tun Sachen mit der Person verwenden:

"Person(Bob,42)" match { 
    case PersonString(person) => println(person.name + " " + person.age) 
    case _ => println("Didn't get a person") 
} 
+2

Ein netter Trick, den ich kürzlich gelernt habe, ist 'import scala.util.control.Exception._' und dann' catching (classOf [NumberFormatException]). Opt (s.toInt) 'oder gar' allCatch.opt (s. toInt) '. –

+1

Das ist eine ziemlich nette kleine Sprache für die Ausnahmebehandlung. Mein Problem ist, dass es für einfache Dinge wie diese nicht wirklich viel spart, was das Tippen angeht, und es kann nur ein weiterer Schritt auf dem Weg sein, wie ein Anfänger ein Beispiel versteht. Davon abgesehen, erhalten Sie meine +1 – Dylan

7

Scala hat keine Typklassen, und in diesem Fall können Sie nicht einmal die Typklasse mit einer vererbten Eigenschaft simulieren, da Eigenschaften nur Methoden für ein Objekt ausdrücken, was bedeutet, dass sie "gehört" werden müssen Eine Klasse, also kann man nicht die Definition eines "Konstruktors, der eine Zeichenkette als einziges Argument nimmt" (was "lesen" in OOP-Sprachen genannt wird) in ein Merkmal einfügen.

Stattdessen müssen Sie selbst Typklassen simulieren. Das ist wie so (entspricht Haskell Code in den Kommentaren) getan:

// class Read a where read :: String -> a 
trait Read[A] { def read(s: String): A } 

// instance Read Person where read = ... parser for Person ... 
implicit object ReadPerson extends Read[Person] { 
    def read(s: String): Person = ... parser for Person ... 
} 

Dann, wenn Sie eine Methode, die auf dem Typenklasse abhängt, müssen Sie es als implizite Kontext angeben:

// readList :: Read a => [String] -> [a] 
// readList ss = map read ss 
def readList[A: Read] (ss: List[String]): List[A] = { 
    val r = implicitly[Read[A]] // Get the class instance of Read for type A 
    ss.map(r.read _) 
} 

der Benutzer möchte wahrscheinlich eine polymorphe Methode, wie dies für einfache Bedienung:

object read { 
    def apply[A: Read](s: String): A = implicitly[Read[A]].read(s) 
} 

Dann kann man nur schreiben:

val person: Person = read[Person]("Person(Bob,42)") 

Ich kenne insbesondere keine Standardimplementierung (en) für diese Typklasse.

Auch ein Disclaimer: Ich habe keinen Scala-Compiler und habe die Sprache seit Jahren nicht mehr benutzt, daher kann ich nicht garantieren, dass dieser Code kompiliert wird.

+3

Ich benutze oft http://www.simplyscala.com, um Scala-Snippets wie dieses zu überprüfen. –

+0

Ich frage mich, ob es so etwas in Scalaz gibt. –

0

Scala nutzt Java Serialisierung stuff, ohne String Darstellung.

+0

Dinge haben sich in der Zwischenzeit geändert, siehe DCKing's Antwort. – thSoft

5

Die Antworten auf diese Frage sind etwas veraltet. Scala hat einige neue Features, insbesondere Typklassen und Makros, aufgenommen, um dies einfacher zu ermöglichen.

Mit der Scala Pickling library können Sie serialisieren/deserialisieren beliebige Klassen zu und von verschiedenen Serialisierungsformaten:

import scala.pickling._ 
import json._ 

case class Person(name: String, age: Int) 
val person1 = Person("Bob", 42) 
val str = person1.pickle.value // { tpe: "Person", name: "Bob", age: 42 } 
val person2 = JSONPickle(str).unpickle[Person] 

assert(person1 == person2) // Works! 

Die Serializer/Deserializer bei der Kompilierung automatisch generiert werden, so dass keine Reflexion! Wenn Sie Fallklassen in einem bestimmten Format analysieren müssen (z. B. das Format der Fallklasse toString), können Sie dieses System um eigene Formate erweitern.

+0

Die Bibliothek greift auf Reflektion zurück, wie oben bei Vanilla-Anwendungsfällen. – matanster

1

Die Bibliothek uPickle bietet eine Lösung für dieses Problem.