17

我想读取一个字符串作为案例类的实例。例如,如果函数被命名为“read”,它会让我执行以下操作:

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

这与 Haskell 中的读取类型类的行为相同。

4

6 回答 6

14

dflemstr 对设置实际方法的read回答更多——我将为实际的解析方法回答更多。

我的方法有两个对象可以在 scala 的模式匹配块中使用。AsInt让您可以匹配代表Ints 的字符串,并且PersonString是反序列化的实际实现Person

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
  }
}

神奇之处在于unapply方法,scala 有语法糖。所以使用这个PersonString对象,你可以做

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

或者你可以使用模式匹配块来处理这个人:

"Person(Bob,42)" match {
  case PersonString(person) => println(person.name + " " + person.age)
  case _ => println("Didn't get a person")
}
于 2012-05-29T19:10:55.340 回答
9

Scala 没有类型类,在这种情况下,您甚至无法使用继承自的 trait 来模拟类型类,因为 trait 仅表示对象上的方法,这意味着它们必须由类“拥有”,所以您不能将“将字符串作为唯一参数的构造函数”(在 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 编译器,并且多年没有使用该语言,所以我不能保证这段代码可以编译。

于 2012-05-29T18:53:11.693 回答
8

开始,可以通过取消应用字符串插值器Scala 2.13来匹配Strings :

// case class Person(name: String, age: Int)
"Person(Bob,42)" match { case s"Person($name,$age)" => Person(name, age.toInt) }
// Person("Bob", 42)

请注意,您也可以提取器中使用regexes 。

在这种情况下,例如有助于匹配“Person(Bob, 42)”(带有前导空格的年龄)并强制年龄为整数:

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))
于 2019-04-06T19:44:19.720 回答
6

这个问题的答案有些过时了。Scala 采用了一些新特性,尤其是类型类和宏,以使这更容易实现。

使用Scala Pickling 库,您可以将任意类序列化/反序列化为各种序列化格式:

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!

序列化器/反序列化器是在编译时自动生成的,所以没有反射!如果您需要使用特定格式(例如案例类toString格式)解析案例类,您可以使用自己的格式扩展此系统。

于 2014-05-12T13:41:14.457 回答
1

uPickle库为这个问题提供了解决方案

于 2015-01-27T14:57:02.473 回答
-1

Scala 使用 Java 的序列化东西,没有String表示。

于 2012-05-29T21:16:50.847 回答