Reactormonk
Reactormonk

Reputation: 21730

Parse List[String] into HList

I would like to write def parse[T <: HList](list: List[String]): Validation[T]. list could be List("fooid", "barid"), and T FooId :: BarId :: HNil, and a typeclass Parse[T] which implements String => Validation[FooId]. How would I write said parse, which parses the list into T? I'm not sure how to summon the implicit typeclasses for each of the elements of T.

Upvotes: 1

Views: 396

Answers (1)

Peter Neyens
Peter Neyens

Reputation: 9820

We can adapt the code from shapeless.ops.traversable.FromTraversable.

I'm not sure what your Validation type is, but I used it as an alias for scalaz.ValidationNel[String, A] below (mostly so I could use the string syntax to easily give me some Parse instances).

import scalaz.{Validation => _, _}, Scalaz._

type Validation[A] = ValidationNel[String, A]

trait Parse[T] {
  def apply(s: String): Validation[T]
}

object Parse {
  def fromScalazParse[E <: Exception, T](f: String => scalaz.Validation[E, T]) =
    new Parse[T] {
      def apply(s: String): Validation[T] =
        f(s).leftMap(_.getMessage).toValidationNel
    }

  implicit val booleanParse = fromScalazParse(_.parseBoolean)
  implicit val intParse     = fromScalazParse(_.parseInt)
  implicit val doubleParse  = fromScalazParse(_.parseDouble)
}

With the Parser type class sorted, we can now create a type class based on FromTraversable to parse a List[String] and give us a Validation[A :: B :: HNil] :

import shapeless._
import scala.collection.GenTraversable

trait FromTraversableParsed[Out <: HList] extends Serializable {
  def apply(l: GenTraversable[String]) : Validation[Out]
}

object FromTraversableParsed {
  def apply[Out <: HList](implicit from: FromTraversableParsed[Out]) = from

  implicit val hnilFromTraversableParsed = 
    new FromTraversableParsed[HNil] {
      def apply(l: GenTraversable[String]): Validation[HNil] =
        if(l.isEmpty) HNil.successNel[String]
        else "Traversable is not empty".failureNel[HNil]
    }

  implicit def hlistFromTraversableParsed[OutH, OutT <: HList](implicit 
    ftpT: FromTraversableParsed[OutT],
    parseH: Parse[OutH]
  ): FromTraversableParsed[OutH :: OutT] = 
    new FromTraversableParsed[OutH :: OutT] {
      def apply(l : GenTraversable[String]) : Validation[OutH :: OutT] =
        if(l.isEmpty) "Empty traversable".failureNel[OutH :: OutT]
        else (parseH(l.head) |@| ftpT(l.tail))(_ :: _)
    }
}

We can add some syntax to make using FromTraversableParsed a little bit easier :

implicit class ParseStringListOps(val strings: List[String]) extends AnyVal {
  def parse[L <: HList](implicit ftp: FromTraversableParsed[L]): Validation[L] =
    ftp(strings)
}

Now we can do :

List("1", "true", "3.0").parse[Int :: Boolean :: Double :: HNil]
// Validation[Int :: Boolean :: Double :: HNil] = Success(1 :: true :: 3.0 :: HNil)

Upvotes: 2

Related Questions