Подтвердить что ты не робот

Прочтите объект класса класса из строки в Scala (что-то вроде Haskell "read" typeclass)

Я хотел бы прочитать строку как экземпляр класса case. Например, если бы функция называлась "read", она позволила бы мне сделать следующее:

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

Это то же поведение, что и для класса чтения типа в Haskell.

4b9b3361

Ответ 1

dflemstr больше ответил на настройку фактического метода read - я отвечу больше за фактический метод синтаксического анализа.

Мой подход имеет два объекта, которые можно использовать в блоках соответствия шаблонов scala. AsInt позволяет сопоставлять строки, представляющие Int s, а 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")
}

Ответ 2

Scala не имеет классов типов, и в этом случае вы даже не можете имитировать класс типа с унаследованным признаком, потому что черты только выражают методы на объекте, что означает, что они должны быть "принадлежащими", по классу, поэтому вы не можете поместить определение "конструктора, который принимает строку как единственный аргумент" (это то, что "чтение" может быть вызвано на языках ООП) в признаке.

Вместо этого вам нужно самостоятельно имитировать классы классов. Это делается так (эквивалентный код 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 и я не использовал этот язык в течение многих лет, поэтому я не могу гарантировать, что этот код компилируется.

Ответ 3

Ответы на этот вопрос несколько устарели. В 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), вы можете расширить эту систему своими собственными форматами.

Ответ 4

Библиотека uPickle предлагает решение этой проблемы.

Ответ 5

Scala использует материал сериализации Java без представления String.

Ответ 6

Начиная с Scala 2.13, можно сопоставить String с шаблоном, отменив интерполяцию строки:

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

Обратите внимание, что вы также можете использовать regex в экстракторе.

Что в этом случае помогает, например, сопоставить "Person (Bob, 42)" (age с начальным пробелом) и заставить age быть целым числом:

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