从 Scala 中的字符串读取 case 类对象(类似于 Haskell 的“读取”类型类)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10804581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:11:44  来源:igfitidea点击:

Read case class object from string in Scala (something like Haskell's "read" typeclass)

scalahaskellserialization

提问by emchristiansen

I'd like to read a string as an instance of a case class. For example, if the function were named "read" it would let me do the following:

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

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

This is the same behavior as the read typeclass in Haskell.

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

采纳答案by Dylan

dflemstr answered more towards setting up the actual readmethod- I'll answer more for the actual parsing method.

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

My approach has two objects that can be used in scala's pattern matching blocks. AsIntlets you match against strings that represent Ints, and PersonStringis the actual implementation for Persondeserialization.

我的方法有两个可用于 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
  }
}

The magic is in the unapplymethod, which scala has syntax sugar for. So using the PersonStringobject, you could do

神奇之处在于unapply方法,Scala 为其提供了语法糖。所以使用PersonString对象,你可以做

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

回答by dflemstr

Scala does not have type classes, and in this case, you cannot even simulate the type class with a trait that is inherited from, because traits only express methods on an object, meaning that they have to be "owned" by a class, so you cannot put the definition of a "constructor that takes a string as the only argument" (which is what "read" might be called in OOP languages) in a trait.

Scala 没有类型类,在这种情况下,你甚至不能用继承自的 trait 来模拟类型类,因为 trait 只表示对象上的方法,这意味着它们必须被一个类“拥有”,所以您不能将“将字符串作为唯一参数的构造函数”的定义(在 OOP 语言中可能称为“读取”)放在特征中。

Instead, you have to simulate type classes yourself. This is done like so (equivalent Haskell code in comments):

相反,您必须自己模拟类型类。这是这样做的(注释中等效的 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 ...
}

Then, when you have a method that depends on the type class, you have to specify it as an implicit context:

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

// 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 _)
}

The user would probably like a polymorphic method like this for ease of use:

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

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

Then one can just write:

然后就可以写:

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

I am not aware of any standard implementation(s) for this type class, in particular.

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

Also, a disclaimer: I don't have a Scala compiler and haven't used the language for years, so I can't guarantee that this code compiles.

另外,免责声明:我没有 Scala 编译器,并且多年未使用该语言,因此我不能保证此代码可以编译。

回答by DCKing

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.

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

Using the Scala Pickling library, you can serialize/deserialize arbitrary classes to and from various serialization formats:

使用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!

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 toStringformat), you can extend this system with your own formats.

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

回答by Xavier Guihot

Starting Scala 2.13, it's possible to pattern match a Strings by unapplying a string interpolator:

开始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)


Note that you can also use regexes withinthe extractor.

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

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:

在这种情况下,这有助于例如匹配“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))

回答by thSoft

The uPicklelibrary offers a solution for this problem.

uPickle库提供了一个解决这个问题。

回答by Daniel C. Sobral

Scala uses Java's serialization stuff, with no Stringrepresentation.

Scala 使用 Java 的序列化内容,没有String表示。