Clean up signatures with long implicit parameter lists - scala

Is there an elegant solution to somehow clean up implicit parameter lists making signatures more concise?
I have code like this:
import shapeless._
import shapeless.HList._
import shapeless.ops.hlist._
import shapeless.poly._
trait T[I, O] extends (I => O)
trait Validator[P]
object Validator{
def apply[P] = new Validator[P]{}
}
object valid extends Poly1 {
implicit def caseFunction[In, Out] = at[T[In, Out]](f => Validator[In])
}
object isValid extends Poly2 {
implicit def caseFolder[Last, New] = at[Validator[Last], T[Last, New]]{(v, n) => Validator[New]}
}
object mkTask extends Poly1 {
implicit def caseT[In, Out] = at[T[In, Out]](x => x)
implicit def caseFunction[In, Out] = at[In => Out](f => T[In, Out](f))
}
object Pipeline {
def apply[H <: HList, Head, Res, MapRes <: HList](steps: H)
(implicit
mapper: Mapper.Aux[mkTask.type,H, MapRes],
isCons: IsHCons.Aux[MapRes, Head, _],
cse: Case.Aux[valid.type, Head :: HNil, Res],
folder: LeftFolder[MapRes, Res, isValid.type]
): MapRes = {
val wrapped = (steps map mkTask)
wrapped.foldLeft(valid(wrapped.head))(isValid)
wrapped
}
}
// just for sugar
def T[I, O](f: I => O) = new T[I, O] {
override def apply(v1: I): O = f(v1)
}
Pipeline(T((x:Int) => "a") :: T((x:String) => 5) :: HNil) // compiles OK
Pipeline(((x:Int) => "a") :: ((x:String) => 5) :: HNil) // compiles OK
// Pipeline("abc" :: "5" :: HNil) // doesn't compile
// can we show an error like "Parameters are not of shape ( _ => _ ) or T[_,_]"?
// Pipeline(T((x: Int) => "a") :: T((x: Long) => 4) :: HNil) // doesn't compile
// can we show an error like "Sequentiality constraint failed"?
And I also want to add a couple of implicit params necessary for the library's functionality (to the Pipeline.apply method), but the signature is already huge. I am worried about the ease of understanding for other developers - is there a "best practice" way to structure these params?
Edit: What I mean is the implicit parameters fall into different categories. In this example: mapper ensures proper content types, isCons, cse and folder ensure a sequential constraint on input, and I would like to add implicits representing "doability" of the business logic. How should they be grouped, is it possible to do in a readable format?
Edit2: Would it be possible to somehow alert the library's user, as to which constraint is violated? E.g. either the types in the HList are wrong, or the sequentiality constraint is not held, or he lacks the proper "business logic" implicits?

My suggestion was to use an implict case class that contains that configuration:
case class PipelineArgs(mapper: Mapper.Aux[mkTask.type,H, MapRes] = DEFAULTMAPPER,
isCons: IsHCons.Aux[MapRes, Head, _] = DEFAULTISCON,
cse: Case.Aux[valid.type, Head :: HNil, Res] = DEFAULTCSE,
folder: LeftFolder[MapRes, Res, isValid.type] = DEFAULTFOLDER) {
require (YOUR TESTING LOGIC, YOUR ERROR MESSAGE)
}
object Pipeline {
def apply[H <: HList, Head, Res, MapRes <: HList](steps: H)
(implicit args:PipelineArgs) = {
val wrapped = (steps map mkTask)
wrapped.foldLeft(valid(wrapped.head))(isValid)
wrapped
}
It doesn't help much w.r.t. clarity (but don't worry, I have seen worse), but it helps at notifying the user he's messing up at the creation of the args instance as you can a) put default values to the missing arguments in the CClass constructor b) put a number of "require" clauses.

Thanks to #Diego's answer, I have come up with the following code which works quite nicely:
import scala.annotation.implicitNotFound
import shapeless._
import shapeless.HList._
import shapeless.ops.hlist._
import shapeless.poly._
trait T[I, O] extends (I => O)
trait Validator[P]
object Validator{
def apply[P] = new Validator[P]{}
}
object valid extends Poly1 {
implicit def caseFunction[In, Out] = at[T[In, Out]](f => Validator[In])
}
object isValid extends Poly2 {
implicit def caseFolder[Last, New] = at[Validator[Last], T[Last, New]]{(v, n) => Validator[New]}
}
object mkTask extends Poly1 {
implicit def caseT[In, Out] = at[T[In, Out]](x => x)
implicit def caseFunction[In, Out] = at[In => Out](f => T[In, Out](f))
}
#implicitNotFound("Type constraint violated, elements must be of shape: (_ => _) or T[_, _]")
case class PipelineTypeConstraint[X, H <: HList, MapRes <: HList]
(
mapper: Mapper.Aux[X,H, MapRes]
)
implicit def mkPipelineTypeConstraint[X, H <: HList, MapRes <: HList]
(implicit mapper: Mapper.Aux[X,H, MapRes]) = PipelineTypeConstraint(mapper)
#implicitNotFound("Sequentiality violated, elements must follow: _[A, B] :: _[B, C] :: _[C, D] :: ... :: HNil")
case class PipelineSequentialityConstraint[Head, CRes, MapRes<: HList, ValidT, IsValidT]
(
isCons: IsHCons.Aux[MapRes, Head, _ <: HList],
cse: Case.Aux[ValidT, Head :: HNil, CRes],
folder: LeftFolder[MapRes, CRes, IsValidT]
)
implicit def mkPipelineSequentialityConstraint[Head, CRes, MapRes <: HList, ValidT, IsValidT]
(implicit isCons: IsHCons.Aux[MapRes, Head, _ <: HList],
cse: Case.Aux[ValidT, Head :: HNil, CRes],
folder: LeftFolder[MapRes, CRes, IsValidT]) = PipelineSequentialityConstraint(isCons, cse, folder)
object Pipeline {
def apply[H <: HList, Head, CRes, MapRes <: HList](steps: H)
(implicit
typeConstraint: PipelineTypeConstraint[mkTask.type, H, MapRes],
sequentialityConstraint: PipelineSequentialityConstraint[Head, CRes, MapRes, valid.type, isValid.type]
): MapRes = {
implicit val mapper = typeConstraint.mapper
implicit val isCons = sequentialityConstraint.isCons
implicit val cse = sequentialityConstraint.cse
implicit val folder = sequentialityConstraint.folder
val wrapped = (steps map mkTask)
wrapped.foldLeft(valid(wrapped.head))(isValid)
wrapped
}
}
// just for sugar
def T[I, O](f: I => O) = new T[I, O] {
override def apply(v1: I): O = f(v1)
}
Pipeline(T((x:Int) => "a") :: T((x:String) => 5) :: HNil) // compiles OK
Pipeline(((x:Int) => "a") :: ((x:String) => 5) :: HNil) // compiles OK
Pipeline(5 :: "abc" :: HNil)
// error = "Type constraint violated, elements must be of shape: (_ => _) or T[_, _]
Pipeline(T((x: Int) => "a") :: T((x: Long) => 4) :: HNil)
// error = "Sequentiality violated, elements must follow: (_[A, B] :: _[B, C] :: _[C, D] :: ... :: HNil"

Related

Is it possible to have a variable type list in Scala?

Is there a way of having a unknown number of types for a generic method? I'm currently experimenting with the Scala Type System and could not find any solution to this.
Classic varargs for parameters look like this:
def printAll(strings: String*) {
strings.foreach(println)
}
But my question is, if there is something like that:
def magic[TYPES*](...) {
for(t <- TYPES){
typeOf[t] ...
}
}
edit:
The idea was, to implement a way to create Lists in square brackets like this:
def magicList[A: TypeTag, B: TypeTag, C: TypeTag, D: TypeTag]: List[Any] = {
val a = typeOf[A] match { case ConstantType(a) => a.value }
val b = typeOf[B] match { case ConstantType(b) => b.value }
val c = typeOf[C] match { case ConstantType(c) => c.value }
val d = typeOf[D] match { case ConstantType(d) => d.value }
List(a,b,c,d)
}
magicList[1,2,3,4] == List(1,2,3,4)
I'm afraid you can't have [TYPES*] in Scala.
If having [L <: HList] (HList is heterogeneous list) is enough for you then with Shapeless you can do
import shapeless.{::, HList, HNil, Poly0, Poly1, Witness}
import shapeless.ops.hlist.{FillWith, Mapper}
object witnessPoly extends Poly1 {
// implicit def cse[A](implicit witness: Witness.Aux[A]): Case.Aux[A, A] = at(_ => witness.value)
implicit def cse[A <: Singleton](implicit valueOf: ValueOf[A]): Case.Aux[A, A] = at(_ => valueOf.value) // scala 2.13
}
object nullPoly extends Poly0 {
implicit def default[A]: ProductCase.Aux[HNil, A] = at(null.asInstanceOf[A])
}
def magic[L <: HList](implicit
mapper: Mapper.Aux[witnessPoly.type, L, L],
fillWith: FillWith[nullPoly.type, L]
): L = mapper(fillWith())
magic[Witness.`1`.T :: Witness.`"a"`.T :: Witness.`true`.T :: HNil] // 1 :: a :: true :: HNil
magic[1 :: "a" :: true :: HNil] // scala 2.13 // 1 :: a :: true :: HNil
magic[1 :: 2 :: 3 :: 4 :: HNil] // scala 2.13 // 1 :: 2 :: 3 :: 4 :: HNil
Actually magic can be defined even simpler
import shapeless.ops.hlist.Reify
def magic[L <: HList](implicit reify: Reify.Aux[L, L]): L = reify()

Shapeless and annotations

I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T. I followed the answer provided here and this gist.
I'm using scala 2.11.12 and shapeless 2.3.3.
Hereafter is my code:
import shapeless._
import shapeless.ops.hlist._
case class MyAnnotation(func: String) extends scala.annotation.StaticAnnotation
trait Modifier[T] {
def modify(t: T): T
}
object Modifier {
def apply[A: Modifier]: Modifier[A] = implicitly[Modifier[A]]
def create[T](func: T => T): Modifier[T] = new Modifier[T] { override def modify(t: T): T = func(t) }
private def id[T](t: T) = t
implicit val stringModifier: Modifier[String] = create(id)
implicit val booleanModifier: Modifier[Boolean] = create(id)
implicit val byteModifier: Modifier[Byte] = create(id)
implicit val charModifier: Modifier[Char] = create(id)
implicit val doubleModifier: Modifier[Double] = create(id)
implicit val floatModifier: Modifier[Float] = create(id)
implicit val intModifier: Modifier[Int] = create(id)
implicit val longModifier: Modifier[Long] = create(id)
implicit val shortModifier: Modifier[Short] = create(id)
implicit val hnilModifier: Modifier[HNil] = create(id)
implicit def hlistModifier[H, T <: HList, AL <: HList](
implicit
hser: Lazy[Modifier[H]],
tser: Modifier[T]
): Modifier[H :: T] = new Modifier[H :: T] {
override def modify(ht: H :: T): H :: T = {
ht match {
case h :: t =>
hser.value.modify(h) :: tser.modify(t)
}
}
}
implicit val cnilModifier: Modifier[CNil] = create(id)
implicit def coproductModifier[L, R <: Coproduct](
implicit
lser: Lazy[Modifier[L]],
rser: Modifier[R]
): Modifier[L :+: R] = new Modifier[L :+: R] {
override def modify(t: L :+: R): L :+: R = t match {
case Inl(l) => Inl(lser.value.modify(l))
case Inr(r) => Inr(rser.modify(r))
}
}
object Collector extends Poly2 {
implicit def myCase[ACC <: HList, E] = at[(E, Option[MyAnnotation]), ACC] {
case ((e, None), acc) => e :: acc
case ((e, Some(MyAnnotation(func))), acc) => {
println(func)
e :: acc
}
}
}
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder[ZL, HNil.type, Collector.type]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil)(Collector)
println(modified)
val typed = gen.from(generic) // temporary
typed
}
}
}
The code above compiles. However, when instanciating a Modifier in a test:
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly
the test file does not compile and give the following error:
[error] ambiguous implicit values:
[error] both value StringCanBuildFrom in object Predef of type =>
scala.collection.generic.CanBuildFrom[String,Char,String]
[error] and method $conforms in object Predef of type [A]=> <:<[A,A]
[error] match expected type T
[error] val ser1: Modifier[Test] = implicitly
The problem seems to come from the right folder definition: when removing rightFolder from the list of implicits in genericModifier, then it works:
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL]/*,
rightFolder: RightFolder[ZL, HNil.type, Collector.type]*/
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
/*val modified = zipped.foldRight(HNil)(Collector)
println(modified)*/
val typed = gen.from(generic) // temporary
typed
}
}
What is wrong?
There are several mistakes in your code:
defining Poly just for Option is too rough (pattern matching is performed at runtime and compiler should know definitions for Some and None at compile time)
HNil should be instead of HNil.type and HNil : HNil instead of HNil (types HNil and HNil.type are different)
compiler doesn't know that RightFolder actually returns the original HList type, so you should use RightFolder.Aux type.
Correct code is
import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation
object App {
case class MyAnnotation(func: String) extends StaticAnnotation
object Collector extends Poly2 {
// implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
// case ((e, None), acc) => e :: acc
// case ((e, Some(MyAnnotation(func))), acc) => {
// println(func)
// e :: acc
// }
// }
implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
case ((e, Some(MyAnnotation(func))), acc) =>
println(func)
e :: acc
}
implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
case ((e, None), acc) => e :: acc
}
}
trait Modifier[T] {
def modify(t: T): T
}
implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
println(modified)
val typed = gen.from(modified)
typed
}
}
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly[Modifier[Test]]
def main(args: Array[String]): Unit = {
val test1 = modifier.modify(test) // prints "sha1"
println(test1) // Test(A,B)
}
}

HList mapping can't define type

With the following, I'm trying to get all the values of Parameter in an HList:
import shapeless._
case class Parameter[T](value: T)
trait ParameterOperations[Params <: HList, ParamValues <: HList] {
def values(params: Params): ParamValues
}
object ParameterOperations {
implicit val hNil = new ParameterOperations[HNil, HNil] {
override def values(params: HNil) = HNil
}
implicit def hCons[HeadParam <: Parameter[HeadParamValue], TailParams <: HList, HeadParamValue, TailParamValues <: HList](
implicit tailParamOperations: ParameterOperations[TailParams, TailParamValues]
) = new ParameterOperations[HeadParam :: TailParams, HeadParamValue :: TailParamValues] {
override def values(params: HeadParam :: TailParams): HeadParamValue :: TailParamValues =
params.head.value :: tailParamOperations.values(params.tail)
}
}
object Test extends App {
def getValues[Params <: HList, ParamValues <: HList](params: Params)(
implicit parameterOperations: ParameterOperations[Params, ParamValues]
) = parameterOperations.values(params)
val b = getValues(HList(Parameter(1), Parameter(true)))
println(b)
}
I'm getting the following error:
[error] /Users/joangoyeau/Code/autowire/autowire/jvm/src/main/scala/Test.scala:30: could not find implicit value for parameter parameterOperations: ParameterOperations[shapeless.::[Parameter[Int],shapeless.::[Parameter[Boolean],shapeless.HNil]],ParamValues]
[error] val b = getValues(HList(Parameter(1), Parameter(true)))
[error] ^
Isn't ParamValues supposed to be defined by the implicit ParameterOperations?
Either specify type parameters explicitly
val b = getValues[Parameter[Int] :: Parameter[Boolean] :: HNil, Int :: Boolean :: HNil](HList(Parameter(1), Parameter(true)))
or exclude HeadParam from implicit hCons (HeadParam is not just <: Parameter[HeadParamValue], actually it is Parameter[HeadParamValue])
implicit def hCons[TailParams <: HList, HeadParamValue, TailParamValues <: HList](
implicit tailParamOperations: ParameterOperations[TailParams, TailParamValues]
) = new ParameterOperations[Parameter[HeadParamValue] :: TailParams, HeadParamValue :: TailParamValues] {
override def values(params: Parameter[HeadParamValue] :: TailParams): HeadParamValue :: TailParamValues =
params.head.value :: tailParamOperations.values(params.tail)
}
By the way, instead of your custom type class ParameterOperations you can use standard shapeless.ops.hlist.Comapped and shapeless.ops.hlist.NatTRel
object paramToIdNatTransform extends (Parameter ~> Id) {
override def apply[T](param: Parameter[T]): T = param.value
}
def getValues[Params <: HList, ParamValues <: HList](params: Params)(implicit
comapped: Comapped.Aux[Params, Parameter, ParamValues],
natTRel: NatTRel[Params, Parameter, ParamValues, Id]
): ParamValues = natTRel.map(paramToIdNatTransform, params)
val b = getValues(HList(Parameter(1), Parameter(true)))
println(b) // 1 :: true :: HNil
Actually it's even simpler, shapeless.ops.hlist.Mapper is enough, so you can write
val b = HList(Parameter(1), Parameter(true)).map(paramToIdNatTransform)
println(b) // 1 :: true :: HNil

Poly function from monomorphic functions that operate on coproduct

I'm working on an API that should enable to build a shapeless Poly1 function dynamically from standard monomorphic functions that operate on types of some coproduct.
The goal is to expose a simple method that receives a function as:
type FooCoproduct = Foo :+: Bar :+: CNil
def addF[E](f: E => E)(implicit ev: Inject[FooCoproduct, E]) = ???
and accumulate these functions in order to build a total Poly1 function covering all types in the coproduct. The evidence ev here is to force that the type paremeter E is a type in the coproduct.
After testing several approaches, including generic derivation of typeclasses, the most promising one has led me to accumulate these monomorphic functions in an HList and try to resolve the one that applies by means of a Selector. This is probably better understood by example:
object CoproductSample extends App {
import shapeless.{ :+:, CNil, Coproduct, HList, HNil, Poly1 }
import shapeless.ops.coproduct.Inject
import shapeless.ops.hlist.Selector
class Builder[A <: Coproduct] {
def accum[B](f: B => B, hl: HList)(implicit ev: Inject[A, B]) = f :: hl
class PolyBuilder[L <: HList](hl: L) extends Poly1 {
implicit def run[T](implicit ev: Selector[L, T => T]) =
at[T](hl.select[T => T])
}
}
type Cop = Int :+: String :+: CNil
val builder = new Builder[Cop]
val hl1 = builder.accum((i: Int) => i + 1, HNil)
val hl2 = builder.accum((s: String) => s + "one", hl1)
object pf extends builder.PolyBuilder(hl2)
val rInt = Coproduct[Cop](10).fold(pf)
val rStr = Coproduct[Cop]("ten").fold(pf)
}
This code doesn't compile with the message:
could not find implicit value for parameter folder:
shapeless.ops.coproduct.Folder[CoproductSample.pf.type, CoproductSample.Cop]
I suppose that I need to provide a Selector[L, T => T] where L is the type of the accumulated HList but I can't come up with the way to do this. On the other hand, I have the feeling that there must be a simpler solution to my problem.
Any help would be appreciated.
Update
After doing some more research I've come up with a solution that almost works. Unfortunately I'm not able to track the result type properly.
object CoproductSample {
import shapeless.{ CNil, Coproduct, HList, HNil, Inl, Inr, Poly2, ::, :+: }
// Accumulates ordinary functions from A => A in an HList
def accum[A, L <: HList](f: A => A, hl: L): (A => A) :: L = f :: hl
// A poly2 function that evaluates some monomorphic function present in
// an HList for certain value that satifies the signature of this function
object PolyEval extends Poly2 {
implicit def hnilCase[A]: Case.Aux[A, HNil, Option[A]] =
at[A, HNil]((a, l) => None)
implicit def hheadCaseSuccess[A, T <: HList]: Case.Aux[A, (A => A) :: T, Option[A]] =
at[A, (A => A) :: T]((a: A, l: (A => A) :: T) => Option(l.head(a)))
implicit def hheadCaseFail[A, H, T <: HList](
implicit tail: Case.Aux[A, T, Option[A]]
): Case.Aux[A, (H => H) :: T, Option[A]] =
at[A, (H => H) :: T]((a: A, l: (H => H) :: T) => PolyEval(a, l.tail))
}
// A poly2 function that uses `PolyEval` for evaluating a value present in
// a coproduct against an HList of monomorphic functions
object PolyEvalCop extends Poly2 {
implicit def cnilCase[A <: CNil, L <: HList]: Case.Aux[A, L, Option[A]] =
at[A, L]((a, l) => sys.error("Impossible!"))
implicit def cconsCase[H, T <: Coproduct, L <: HList](
implicit head: PolyEval.Case.Aux[H, L, Option[H]],
tail: Case[T, L]) // What is the return type here???)
= at[H :+: T, L]((c, l) =>
c match {
case Inl(h) => PolyEval(h, l)
case Inr(t) => PolyEvalCop(t, l)
})
}
}
Console session:
scala> import shapeless._, CoproductSample._
import shapeless._
import CoproductSample._
scala> case class Foo(i: Int); case class Bar(s: String)
defined class Foo
defined class Bar
scala> val f = (foo: Foo) => foo.copy(i = foo.i * 2)
f: Foo => Foo = <function1>
scala> val g = (bar: Bar) => bar.copy(s = bar.s + "_changed!")
g: Bar => Bar = <function1>
scala> val hl = accum(g, accum(f, HNil))
hl: shapeless.::[Bar => Bar,shapeless.::[Foo => Foo,shapeless.HNil.type]] = <function1> :: <function1> :: HNil
scala> type C = Foo :+: Bar :+: CNil
defined type alias C
scala> PolyEvalCop(Coproduct[C](Foo(10)), hl)
res1: Any = Some(Foo(20))
scala> PolyEvalCop(Coproduct[C](Bar("bar")), hl)
res2: Any = Some(Bar(bar_changed!))
The result type is not properly tracked and it's resolved as Any.
From the signature of addF it looks like you want to map over the Coproduct, i.e. modify it's value and stay on a coproduct, i.e. def add[E](e:E):E, if that were the case this would work:
# {
trait Add[E]{
def add(e:E):E
}
object Add{
def apply[E:Add]:Add[E] = implicitly[Add[E]]
implicit object cnil extends Add[CNil] {
def add(e:CNil) = throw new RuntimeException("Impossible")
}
implicit def coproduct[H, T <: Coproduct](
implicit
addH: Add[H],
addT:Add[T],
basis: ops.coproduct.Basis[H :+: T,T]
):Add[H :+: T] = new Add[H :+: T]{
def add(e: H :+: T) = e match {
case Inl(h) => Coproduct[H :+: T](addH.add(h)) // to stay in the Coproduct
case Inr(t) => addT.add(t).embed[H :+: T] // to stay in the coproduct
}
}
}
}
defined trait Add
defined object Add
# implicit def addString = new Add[String] {
def add(e:String) = e + "-ah"
}
defined function addString
# implicit def addInt = new Add[Int] {
def add(e:Int) = e + 1
}
defined function addInt
# type C = Int :+: String :+: CNil
defined type C
# val i = Coproduct[C](1)
i: C = 1
# Add[C].add(i)
res24: C = 2 // notice that the return type is C
# val s = Coproduct[C]("a")
s: C = a
# Add[C].add(s)
res26: C = a-ah // notice that the return type is C
It obviously works with "plain" types:
# Add[Int].add(1)
res38: Int = 2
The above is equivalent to map; but if you want a fold, i.e. def add[E](e:E):Int, you would just modify these two lines:
case Inl(h) => addH.add(h)
case Inr(t) => addT.add(t)

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