从Scala中的字符串中读取案例类对象(类似于Haskell的"读取"类型类)

emc*_*sen 16 serialization haskell scala

我想读一个字符串作为案例类的实例.例如,如果函数名为"read",它将允许我执行以下操作:

case class Person(name: String, age: Int)
val personString: String = "Person(Bob,42)"
val person: Person = read(personString)
Run Code Online (Sandbox Code Playgroud)

这与Haskell中的读类型类相同.

Dyl*_*lan 14

dflemstr在设置实际read方法时回答得更多 - 我将为实际的解析方法做出更多回答.

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

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
  }
}
Run Code Online (Sandbox Code Playgroud)

魔法在unapply方法中,scala具有语法糖.所以使用该PersonString对象,你可以做到

val person = PersonString.unapply("Person(Bob,42)")
//  person will be Some(Person("Bob", 42))
Run Code Online (Sandbox Code Playgroud)

或者您可以使用模式匹配块来与该人做事:

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

  • 我最近学到的一个很好的技巧是使用`import scala.util.control.Exception._`然后`catch(classOf [NumberFormatException]).opt(s.toInt)`或甚至`allCatch.opt(s.toInt) )`. (3认同)
  • 这是一种用于异常处理的非常好的小语言。我的问题是,对于像这样的简单事情,它并没有真正在打字方面节省多少,它可能只是初学者理解示例的又一步。话虽如此,你会得到我的 +1 (2认同)

dfl*_*str 8

Scala没有类型类,在这种情况下,您甚至无法使用从中继承的特征来模拟类型类,因为特征只表示对象上的方法,这意味着它们必须由类"拥有",因此你不能把"将字符串作为唯一参数的构造函数"的定义(这是在OOP语言中可以调用的"read")在特征中.

相反,您必须自己模拟类型类.这是这样完成的(注释中的等效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 ...
}
Run Code Online (Sandbox Code Playgroud)

然后,当您有一个依赖于类型类的方法时,您必须将其指定为隐式上下文:

// 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 _)
}
Run Code Online (Sandbox Code Playgroud)

用户可能会喜欢这样的多态方法,以方便使用:

object read {
  def apply[A: Read](s: String): A = implicitly[Read[A]].read(s)
}
Run Code Online (Sandbox Code Playgroud)

然后人们可以写:

val person: Person = read[Person]("Person(Bob,42)")
Run Code Online (Sandbox Code Playgroud)

我不知道这种类型的任何标准实现,特别是.

另外,免责声明:我没有Scala编译器并且多年没有使用该语言,所以我无法保证此代码编译.

  • 我经常使用http://www.simplyscala.com/来检查这样的Scala片段. (3认同)

Xav*_*hot 7

开始Scala 2.13,可以String通过不应用字符串插值器来模式匹配s :

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

请注意,您也可以提取器中使用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))
Run Code Online (Sandbox Code Playgroud)


DCK*_*ing 5

这个问题的答案有点过时了.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!
Run Code Online (Sandbox Code Playgroud)

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