marcin_koss
marcin_koss

Reputation: 5882

Create object instance from list of string type arguments

Given this case class case class Location(id: BigInt, lat: Double, lng: Double)

and a list of strings List("87222", "42.9912987", "-93.9557953")

I would like to do something like Location.fromString(listOfString) in a way Location instance is created with string types converted into appropriate types.

Only way I can think of is define fromString method in each case class but I'm looking for a more generic solution each class can inherit. For example, I would have case class Location(...) extends Record, where Record implements fromString that does conversion based on argument types defined in Location class.

Upvotes: 1

Views: 279

Answers (1)

Odomontois
Odomontois

Reputation: 16308

Normally for best performance in such you need to create some macro.

Luckily there is beautiful shapeless library with which you could create such generic reader with near-macro performance in no time using it's generic case class representation

First define typeclass and some instances for reading your fields:

trait Read[T] {
  def fromString(s: String): T
}

implicit object readBigInt extends Read[BigInt] {
  def fromString(s: String): BigInt = BigInt(s)
}

implicit object readDouble extends Read[Double] {
  def fromString(s: String): Double = s.toDouble
}

Next define your main typeclass:

trait ReadSeq[T] {
  def fromStrings(ss: Seq[String]): T
}

Now it's shapeless time (thinking about Dalí?) . We create reader for powerful Heterogenous List, which is almost like List but with of statically known length and element types.
It's no harder than to match simple List. Just define cases for empty list and cons:

import shapeless._

implicit object readHNil extends ReadSeq[HNil] {
  def fromStrings(ss: Seq[String]) = HNil
}

implicit def readHList[X, XS <: HList](implicit head: Read[X], tail: ReadSeq[XS]) =
  new ReadSeq[X :: XS] {
    def fromStrings(ss: Seq[String]) = ss match {
      case s +: rest => (head fromString s) :: (tail fromStrings rest)
    }
  }

Now we can use out-of-the-box macro mapping of HLists and case classes via Generic:

implicit def readCase[C, L <: HList](implicit gen: Generic.Aux[C, L], read: ReadSeq[L]) =
  new ReadSeq[C] {
    def fromStrings(ss: Seq[String]) = gen.from(read.fromStrings(ss))
  }

Finally we could build our typeclass user:

def fromStringSeq[T](ss: Seq[String])(implicit read: ReadSeq[T]) = read.fromStrings(ss)

from this point having

case class Location(id: BigInt, lat: Double, lng: Double)

val repr = List("87222", "42.9912987", "-93.9557953")

you can ensure that

fromStringSeq[Location](repr) == Location(BigInt("87222"), 42.9912987, 93.9557953)

Upvotes: 3

Related Questions