ケースクラスのインスタンスとして文字列を読み取りたいのですが。たとえば、関数の名前が「read」の場合、次のようになります。
case class Person(name: String, age: Int)
val personString: String = "Person(Bob,42)"
val person: Person = read(personString)
これは、Haskellの読み取り型クラスと同じ動作です。
ケースクラスのインスタンスとして文字列を読み取りたいのですが。たとえば、関数の名前が「read」の場合、次のようになります。
case class Person(name: String, age: Int)
val personString: String = "Person(Bob,42)"
val person: Person = read(personString)
これは、Haskellの読み取り型クラスと同じ動作です。
dflemstr answered more towards setting up the actual read
method- I'll answer more for the actual parsing method.
My approach has two objects that can be used in scala's pattern matching blocks. AsInt
lets you match against strings that represent Int
s, and PersonString
is the actual implementation for Person
deserialization.
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
}
}
The magic is in the unapply
method, which scala has syntax sugar for. So using the PersonString
object, you could do
val person = PersonString.unapply("Person(Bob,42)")
// person will be Some(Person("Bob", 42))
or you could use a pattern matching block to do stuff with the person:
"Person(Bob,42)" match {
case PersonString(person) => println(person.name + " " + person.age)
case _ => println("Didn't get a person")
}
Scalaには型クラスがありません。この場合、特性はオブジェクトのメソッドのみを表現するため、継承された特性を使用して型クラスをシミュレートすることもできません。つまり、クラスが「所有」する必要があります。 「文字列を唯一の引数として取るコンストラクター」(これは、OOP言語で「読み取り」と呼ばれる可能性があるもの)の定義を特性に入れることはできません。
代わりに、型クラスを自分でシミュレートする必要があります。これはそのように行われます(コメント内の同等のHaskellコード):
// 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 ...
}
次に、型クラスに依存するメソッドがある場合は、それを暗黙のコンテキストとして指定する必要があります。
// 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 _)
}
ユーザーは、使いやすさのために、おそらく次のようなポリモーフィックメソッドを望んでいます。
object read {
def apply[A: Read](s: String): A = implicitly[Read[A]].read(s)
}
次に、次のように書くことができます。
val person: Person = read[Person]("Person(Bob,42)")
特に、この型クラスの標準的な実装については知りません。
また、免責事項:私はScalaコンパイラーを持っておらず、この言語を何年も使用していないため、このコードがコンパイルされることを保証できません。
Starting Scala 2.13
, it's possible to pattern match a String
s by unapplying a string interpolator:
// case class Person(name: String, age: Int)
"Person(Bob,42)" match { case s"Person($name,$age)" => Person(name, age.toInt) }
// Person("Bob", 42)
Note that you can also use regex
es within the extractor.
Which in this case, helps for instance to match on "Person(Bob, 42)" (age with a leading space) and to force age to be an integer:
val Age = "[ ?*](\\d+)".r
"Person(Bob, 42)" match {
case s"Person($name,${Age(age)})" => Some(Person(name, age.toInt))
case _ => None
}
// Person = Some(Person(Bob,42))
The answers on this question are somewhat outdated. Scala has picked up some new features, notably typeclasses and macros, to make this more easily possible.
Using the Scala Pickling library, you can serialize/deserialize arbitrary classes to and from various serialization formats:
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!
The serializers/deserializers are automatically generated at compile time, so no reflection! If you need to parse case classes using a specific format (such as the case class toString
format), you can extend this system with your own formats.
The uPickle library offers a solution for this problem.
Scala uses Java's serialization stuff, with no String
representation.