I wrote this code
object HList1Test extends App {
import shapeless.{HList, HNil}
import shapeless.syntax._
import shapeless.ops._
def myFunc(list: List[Int], result: HList = HNil) : HList = {
def double (i: Int) : HList = if (i % 2 == 0) 2 * i :: HNil else { (2 * i).toString :: HNil}
list match {
case h :: t => {
myFunc(t, double(h) ::: result)
}
case Nil => result
}
}
println(myFunc(List(1, 2, 3)))
}
But I get error
Error:(16, 33) could not find implicit value for parameter prepend: shapeless.ops.hlist.Prepend[shapeless.HList,shapeless.HList]
myFunc(t, double(h) ::: hlist)
Error:(16, 33) not enough arguments for method :::: (implicit prepend: shapeless.ops.hlist.Prepend[shapeless.HList,shapeless.HList])prepend.Out.
Unspecified value parameter prepend.
myFunc(t, double(h) ::: hlist)
My end goal was to have a HList with "2" :: 4 :: "6" :: HNil
Saying that 2 * i :: HNil and (2 * i).toString :: HNil have type just HList (rather than Int :: HNil and String :: HNil correspondingly) is too rough.
Since double actually returns values of types Int :: HNil or String :: HNil depending on value of its input argument, double is a Poly. So is myFunc.
object double extends Poly1 {
implicit def evenCase[N <: Nat](implicit
mod: Mod.Aux[N, _2, _0],
toInt: ToInt[N]): Case.Aux[N, Int :: HNil] =
at(n => 2 * toInt() :: HNil)
implicit def oddCase[N <: Nat](implicit
mod: Mod.Aux[N, _2, _1],
toInt: ToInt[N]): Case.Aux[N, String :: HNil] =
at(n => (2 * toInt()).toString :: HNil)
}
object myFunc extends Poly2 {
implicit def hNilCase[R <: HList]: Case.Aux[HNil, R, R] = at((_, r) => r)
implicit def hConsCase[H <: Nat, T <: HList,
dblH <: HList, R <: HList,
P <: HList, Out <: HList](implicit
dbl: double.Case.Aux[H, dblH],
prepend: Prepend.Aux[dblH, R, P],
myF: myFunc.Case.Aux[T, P, Out]): Case.Aux[H :: T, R, Out] =
at { case (h :: t, r) => myFunc(t, double(h) ::: r) }
}
println(myFunc(_1 :: _2 :: _3 :: HNil, HNil)) //"6" :: 4 :: "2" :: HNil
Related
I want to define a function that accepts a HList whose elements are such that, for each element t, there is a type T such that t: Either[String, T]. The function, which we will call validate, should have the following behaviour:
If all elements of the parameter are Right, return Right of the result of mapping the parameter with right-projection.
Otherwise, return a Left[List[String]], where the list contains the left-projection for each Left in the parameter.
Examples:
validate (Right (42) :: Right (3.14) :: Right (false) :: HNil)
>> Right (42 :: 3.14 :: false :: HNil)
validate (Right (42) :: Left ("qwerty") :: Left ("uiop") :: HNil)
>> Left (List ("qwerty", "uiop"))
An example use case:
case class Result (foo: Foo, bar: Bar, baz: Baz, qux: Qux)
def getFoo: Either[String, Foo] = ???
def getBar: Either[String, Bar] = ???
def getBaz: Either[String, Baz] = ???
def getQux: Either[String, Qux] = ???
def createResult: Either[String, Result] = {
validate (getFoo :: getBar :: getBaz :: getQux :: HNil) match {
case Right (foo :: bar :: baz :: qux :: HNil) => Right (Result (foo, bar, baz, qux))
case Left (errors) => Left ("The following errors occurred:\n" + errors.mkString ("\n"))
}
}
I'll assume we have some test data like this throughout this answer:
scala> import shapeless.{::, HNil}
import shapeless.{$colon$colon, HNil}
scala> type In = Either[String, Int] :: Either[String, String] :: HNil
defined type alias In
scala> val good: In = Right(123) :: Right("abc") :: HNil
good: In = Right(123) :: Right(abc) :: HNil
scala> val bad: In = Left("error 1") :: Left("error 2") :: HNil
bad: In = Left(error 1) :: Left(error 2) :: HNil
Using a custom type class
There are many ways you could do this. I'd probably use a custom type class that highlights the way instances are built up inductively:
import shapeless.HList
trait Sequence[L <: HList] {
type E
type Out <: HList
def apply(l: L): Either[List[E], Out]
}
object Sequence {
type Aux[L <: HList, E0, Out0 <: HList] = Sequence[L] { type E = E0; type Out = Out0 }
implicit def hnilSequence[E0]: Aux[HNil, E0, HNil] = new Sequence[HNil] {
type E = E0
type Out = HNil
def apply(l: HNil): Either[List[E], HNil] = Right(l)
}
implicit def hconsSequence[H, T <: HList, E0](implicit
ts: Sequence[T] { type E = E0 }
): Aux[Either[E0, H] :: T, E0, H :: ts.Out] = new Sequence[Either[E0, H] :: T] {
type E = E0
type Out = H :: ts.Out
def apply(l: Either[E0, H] :: T): Either[List[E0], H :: ts.Out] =
(l.head, ts(l.tail)) match {
case (Right(h), Right(t)) => Right(h :: t)
case (Left(eh), Left(et)) => Left(eh :: et)
case (Left(eh), _) => Left(List(eh))
case (_, Left(et)) => Left(et)
}
}
}
Then you can write validate like this:
def validate[L <: HList](l: L)(implicit s: Sequence[L]): Either[List[s.E], s.Out] = s(l)
And use it like this:
scala> validate(good)
res0: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> validate(bad)
res1: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Left(List(error 1, error 2))
Note that the static types come out right.
Using a right fold
You could also do it a little more concisely by folding with a Poly2.
import shapeless.Poly2
object combine extends Poly2 {
implicit def eitherCase[H, T, E, OutT <: HList]:
Case.Aux[Either[E, H], Either[List[E], OutT], Either[List[E], H :: OutT]] = at {
case (Right(h), Right(t)) => Right(h :: t)
case (Left(eh), Left(et)) => Left(eh :: et)
case (Left(eh), _) => Left(List(eh))
case (_, Left(et)) => Left(et)
}
}
And then:
scala> good.foldRight(Right(HNil): Either[List[String], HNil])(combine)
res2: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> bad.foldRight(Right(HNil): Either[List[String], HNil])(combine)
res3: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Left(List(error 1, error 2))
I guess this is probably the "right" answer, assuming you want to stick to Shapeless alone. The Poly2 approach just relies on some weird details of implicit resolution (we couldn't define combine as a val, for example) that I personally don't really like.
Using Kittens's sequence
Lastly you could use the Kittens library, which supports sequencing and traversing hlists:
scala> import cats.instances.all._, cats.sequence._
import cats.instances.all._
import cats.sequence._
scala> good.sequence
res4: scala.util.Either[String,Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> bad.sequence
res5: scala.util.Either[String,Int :: String :: shapeless.HNil] = Left(error 1)
Note that this doesn't accumulate errors, though.
If you wanted the most complete possible Typelevel experience I guess you could add a parSequence operation to Kittens that would accumulate errors for an hlist of eithers via the Parallel instance mapping them to Validated (see my blog post here for more detail about how this works). Kittens doesn't currently include this, though.
Update: parallel sequencing
If you want parSequence, it's not actually that much of a nightmare to write it yourself:
import shapeless.HList, shapeless.poly.~>, shapeless.ops.hlist.{Comapped, NatTRel}
import cats.Parallel, cats.instances.all._, cats.sequence.Sequencer
def parSequence[L <: HList, M[_], P[_], PL <: HList, Out](l: L)(implicit
cmp: Comapped[L, M],
par: Parallel.Aux[M, P],
ntr: NatTRel[L, M, PL, P],
seq: Sequencer.Aux[PL, P, Out]
): M[Out] = {
val nt = new (M ~> P) {
def apply[A](a: M[A]): P[A] = par.parallel(a)
}
par.sequential(seq(ntr.map(nt, l)))
}
And then:
scala> parSequence(good)
res0: Either[String,Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> parSequence(bad)
res1: Either[String,Int :: String :: shapeless.HNil] = Left(error 1error 2)
Note that this does accumulate errors, but by concatenating the strings. The Cats-idiomatic way to accumulate errors in a list would look like this:
scala> import cats.syntax.all._
import cats.syntax.all._
scala> val good = 123.rightNel[String] :: "abc".rightNel[String] :: HNil
good: Either[cats.data.NonEmptyList[String],Int] :: Either[cats.data.NonEmptyList[String],String] :: shapeless.HNil = Right(123) :: Right(abc) :: HNil
scala> val bad = "error 1".leftNel[String] :: "error 2".leftNel[Int] :: HNil
bad: Either[cats.data.NonEmptyList[String],String] :: Either[cats.data.NonEmptyList[String],Int] :: shapeless.HNil = Left(NonEmptyList(error 1)) :: Left(NonEmptyList(error 2)) :: HNil
scala> parSequence(good)
res3: Either[cats.data.NonEmptyList[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> parSequence(bad)
res4: Either[cats.data.NonEmptyList[String],String :: Int :: shapeless.HNil] = Left(NonEmptyList(error 1, error 2))
It'd probably be worth opening a PR to add something like this to Kittens.
I managed to arrive at a solution essentially identical to Travis Brown's right-fold solution, with a few additions:
class Validate[E] {
def apply[L <: HList] (hlist: L) (implicit folder: RightFolder[L, Either[List[E], HNil], combine.type]) =
hlist.foldRight (Right (HNil) : Either[List[E], HNil]) (combine)
}
object combine extends Poly2 {
implicit def combine[E, H, T <: HList]
: ProductCase.Aux[Either[E, H] :: Either[List[E], T] :: HNil, Either[List[E], H :: T]] = use {
(elem: Either[E, H], result: Either[List[E], T]) => (elem, result) match {
case (Left (error), Left (errors)) => Left (error :: errors)
case (Left (error), Right (_)) => Left (error :: Nil)
case (Right (_), Left (errors)) => Left (errors)
case (Right (value), Right (values)) => Right (value :: values)
}
}
}
def validate[E] = new Validate[E]
This allows the left type to vary, and allows the syntax:
validate[String] (getFoo :: getBar :: getBaz :: getQux :: HNil) match {
case Right (foo :: bar :: baz :: qux :: HNil) => ???
case Left (errors) => ???
}
Admittedly this is the first time that I have used Poly. It blew my mind to see that this actually worked. Infuriatingly, the static analysis provided by my IDE (IntelliJ) is not clever enough to infer the types of the terms in the match cases.
I am new to shapeless and try to tackle the following problem. I have tuples of different length with Option[(R[A], A)] as elements and would like to kind of filter the tuple so that it results only in Some. Notice that I don't have Some or None at compile time but only Option.
I think I have kind of a recursive problem but cannot describe it. I have something like the following in mind (where I have already converted the tuple into an HList:
def performFinalStep[A1](t: Tuple1[A1]) = ???
def performFinalStep[A1, A2](t: Tuple2[A1, A2]) = ???
...
def reduce[T <: HList, A <: HList](l: Option[(R[A], A)] :: T, acc: A) = {
case h :: HNil => performFinalStep(toTuple(h :: acc))
case h :: t => reduce(t, h :: acc) //does not work as it is not known if T's head is Option[(R[A], A)]
}
reduce((Option(R(1), 2), Option(R("S", "s"))).productElements, HNil)
There are two things I don't know, how do I turn an HList back into a tuple and how can I overcome the typing for T?
In Shapeless converting case classes to HLists and vice versa can be done via shapeless.Generic. And in Scala Tuples are case classes. But there is standard way
(1 :: "a" :: true :: HNil).tupled // (1,a,true)
import shapeless.ops.hlist.Tupler
def toTuple[L <: HList](l : L)(implicit tupler: Tupler[L]): tupler.Out = l.tupled
When you can't express something with a method you create a type class (I don't know your actual type instead of Nothing)
case class R[A](a: A)
trait Reduce[L <: HList, Acc <: HList] {
def apply(l: L, acc: Acc): Nothing
}
object Reduce {
implicit def singletonCase[A, Acc <: HList](implicit
tupler: Tupler[Option[(R[A], A)] :: Acc]): Reduce[Option[(R[A], A)] :: HNil, Acc] =
(l, acc) => l match {
case h :: HNil => performFinalStep(toTuple(h :: acc))
}
implicit def notSingletonCase[H, T <: HList, Acc <: HList](implicit
reduce: Reduce[T, H :: Acc]): Reduce[H :: T, Acc] =
(l, acc) => l match {
case h :: t => reduce(t, h :: acc)
}
}
def reduce[L <: HList, Acc <: HList](l: L, acc: Acc)(implicit
r: Reduce[L, Acc]): Nothing = r(l, acc)
Next trouble is
Error:(39, 27) overloaded method value performFinalStep with alternatives:
[A1, A2](t: (A1, A2))Nothing <and>
[A1](t: (A1,))Nothing
cannot be applied to (tupler.Out)
case h :: HNil => performFinalStep(toTuple(h :: acc))
You can try one more type class
trait PerformFinalStep[P <: Product] {
def apply(t: P): Nothing
}
object PerformFinalStep {
implicit def tuple1[A1]: PerformFinalStep[Tuple1[A1]] = t => ???
implicit def tuple2[A1, A2]: PerformFinalStep[Tuple2[A1, A2]] = t => ???
// ...
}
def performFinalStep[T <: Product](t: T)(implicit
pfs: PerformFinalStep[T]) = pfs(t)
trait Reduce[L <: HList, Acc <: HList] {
def apply(l: L, acc: Acc): Nothing
}
object Reduce {
implicit def singletonCase[A, Acc <: HList, P <: Product](implicit
tupler: Tupler.Aux[Option[(R[A], A)] :: Acc, P],
pfs: PerformFinalStep[P]): Reduce[Option[(R[A], A)] :: HNil, Acc] =
(l, acc) => l match {
case h :: HNil => performFinalStep(toTuple(h :: acc))
}
implicit def notSingletonCase[H, T <: HList, Acc <: HList](implicit
reduce: Reduce[T, H :: Acc]): Reduce[H :: T, Acc] =
(l, acc) => l match {
case h :: t => reduce(t, h :: acc)
}
}
def reduce[L <: HList, Acc <: HList](l: L, acc: Acc)(implicit
r: Reduce[L, Acc]): Nothing = r(l, acc)
Now reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil) produces "could not find implicit value for parameter" but implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]] compiles. If we substitute explicitly
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil)(
implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]]
)
we'll have
Error:(52, 82) type mismatch;
found : Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: shapeless.HNil,shapeless.HNil]
required: Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: shapeless.HNil,shapeless.HNil.type]
Note: shapeless.HNil >: shapeless.HNil.type, but trait Reduce is invariant in type Acc.
You may wish to define Acc as -Acc instead. (SLS 4.5)
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil)(implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]])
So you should call it like
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil : HNil)
Sometimes I need a compile-time proof that each element of H <: HList has type T.
It can be represented by this code:
import shapeless._
#annotation.implicitNotFound("Cannot prove that A =:= ${T} forAll A in ${H}")
trait ForAll[H <: HList, T]
object ForAll {
implicit def head[T]: ForAll[T :: HNil, T] = new ForAll[T :: HNil, T] {}
implicit def tail[T, HT <: HList](implicit ttail: ForAll[HT, T]): ForAll[T :: HT, T] = new ForAll[T :: HT, T] {}
}
// an example
def chain[H <: HList, A](hlist: H)(a: A)(implicit allAreFunctions: ForAll[H, (A => A)]): A = {
def iter(h: HList, ax: A): A = h match {
case HNil => ax
case (f: (A => A)) :: tail => iter(tail, f(ax))
}
iter(hlist, a)
}
val hlist1 = ((_: Int) + 1) :: ((_: Int) * 1) :: ((_: Int) - 2) :: HNil
val hlist2 = ((_: Int).toString) :: ((_: Int) + 1) :: ((_: Int) - 2) ::HNil
chain(hlist1)(1)
// chain(hlist2)(1) doesn't compile
I can ignore compiler warning about possible match error because ForAll instance proves that the code is correct.
Does shapeless implement something like this (or maybe there are a better alternative)?
ForAll is just shapeless.ops.hlist.LeftReducer for properly defined Poly. For example
import shapeless.{::, HNil, Poly2}
import shapeless.ops.hlist.LeftReducer
object myPoly extends Poly2 {
implicit def `case`[A]: Case.Aux[A, A, A] = at((x, y) => x)
}
implicitly[LeftReducer.Aux[Int :: Int :: Int :: Int :: HNil, myPoly.type, Int]]//compiles
implicitly[LeftReducer[Int :: Int :: Int :: Int :: HNil, myPoly.type]]//compiles
// implicitly[LeftReducer.Aux[Int :: Int :: String :: Int :: HNil, myPoly.type, Int]]//doesn't compile
// implicitly[LeftReducer[Int :: Int :: String :: Int :: HNil, myPoly.type]]//doesn't compile
LeftReducer[Int :: Int :: Int :: Int :: HNil, myPoly.type].apply(1 :: 2 :: 3 :: 4 :: HNil)//1
// LeftReducer[Int :: Int :: String :: Int :: HNil, myPoly.type].apply(1 :: 2 :: "a" :: 4 :: HNil)//doesn't compile
(1 :: 2 :: 3 :: 4 :: HNil).reduceLeft(myPoly)//1
// (1 :: 2 :: "a" :: 4 :: HNil).reduceLeft(myPoly)//doesn't compile
I have the following method:
import shapeless._
import shapeless.UnaryTCConstraint._
def method[L <: HList : *->*[Seq]#λ](list: L) = println("checks")
It allows me to ensure the following happens:
val multipleTypes = "abc" :: 1 :: 5.5 :: HNil
val onlyLists = Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil
method(multipleTypes) // throws could not find implicit value ...
method(onlyList) // prints checks
How can I augment method with another parameter list, something like:
def method2[L <: HList : *->*[Seq]#λ, M <: HList](list: L)(builder: M => String) = println("checks")
But with the restriction that the HList M must be of the same size as the HList L and only contain elements of the inner types of the HList L. Let me give an example:
// This should work
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case a :: 1 :: d :: HNil => "works"
}
// This should throw some error at compile time, because the second element is Seq[Int]
// therefore in the builder function I would like the second element to be of type Int.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case a :: true :: d :: HNil => "fails"
}
// This should also fail because the HLists are not of the same length.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case 1 :: d :: HNil => "fails"
}
If I define method2 like this:
def method2[L <: HList : *->*[Seq]#λ](list: L)(builder: L => String) = println("checks")
It almost solves the problem, the only thing that is missing is that builder will have elements of type Seq[T] instead of elements of type T.
This is a case for ops.hlist.Comapped.
You'd want to define method2 as
def method2[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(builder: M => String)
(implicit ev: Comapped.Aux[L, Seq, M])
But this won't work, because the type M should be calculated prior to typechecking the builder argument.
So the actual implementation becomes something like:
class Impl[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(implicit ev: Comapped.Aux[L, Seq, M])
{
def apply(builder: M => String) = println("checks")
}
def method2[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(implicit ev: Comapped.Aux[L, Seq, M]) = new Impl[L, M](list)
And you can't call it directly. You may use an additional apply, or some other method to provide the implicit argument list implicitly:
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil) apply {
case a :: 1 :: d :: HNil => "works"
}
I'm using shapeless in Scala, and I'd like to write a function allPairs that will take two HLists and return an HList of all pairs of elements. For example:
import shapeless._
val list1 = 1 :: "one" :: HNil
val list2 = 2 :: "two" :: HNil
// Has value (1, 2) :: (1, "two") :: ("one", 2) :: ("one", "two") :: HNil
val list3 = allPairs(list1, list2)
Any idea how to do this?
Also, I'd like to emphasize I'm looking for a function, not an inlined block of code.
You can't use a for-comprehension or a combination of map and flatMap with function literals here (as the other answers suggest), since these methods on HList require higher rank functions. If you just have two statically typed lists, this is easy:
import shapeless._
val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
object eachFirst extends Poly1 {
implicit def default[A] = at[A] { a =>
object second extends Poly1 { implicit def default[B] = at[B](a -> _) }
ys map second
}
}
val cartesianProductXsYs = xs flatMap eachFirst
Which gives us the following (appropriately typed):
(1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Writing a method that will do this with HList arguments is trickier. Here's a quick example of how it can be done (with some slightly more general machinery).
I'll start by noting that we can think of finding the Cartesian product of two ordinary lists as "lifting" a function that takes two arguments and returns them as a tuple into the applicative functor for lists. For example, you can write the following in Haskell:
import Control.Applicative (liftA2)
cartesianProd :: [a] -> [b] -> [(a, b)]
cartesianProd = liftA2 (,)
We can write a polymorphic binary function that corresponds to (,) here:
import shapeless._
object tuple extends Poly2 {
implicit def whatever[A, B] = at[A, B] { case (a, b) => (a, b) }
}
And define our example lists again for completeness:
val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
Now we'll work toward a method named liftA2 that will allow us to write the following:
liftA2(tuple)(xs, ys)
And get the correct result. The name liftA2 is a little misleading, since we don't really have an applicative functor instance, and since it's not generic—I'm working on the model of the methods named flatMap and map on HList, and am open to suggestions for something better.
Now we need a type class that will allow us to take a Poly2, partially apply it to something, and map the resulting unary function over an HList:
trait ApplyMapper[HF, A, X <: HList, Out <: HList] {
def apply(a: A, x: X): Out
}
object ApplyMapper {
implicit def hnil[HF, A] = new ApplyMapper[HF, A, HNil, HNil] {
def apply(a: A, x: HNil) = HNil
}
implicit def hlist[HF, A, XH, XT <: HList, OutH, OutT <: HList](implicit
pb: Poly.Pullback2Aux[HF, A, XH, OutH],
am: ApplyMapper[HF, A, XT, OutT]
) = new ApplyMapper[HF, A, XH :: XT, OutH :: OutT] {
def apply(a: A, x: XH :: XT) = pb(a, x.head) :: am(a, x.tail)
}
}
And now a type class to help with the lifting:
trait LiftA2[HF, X <: HList, Y <: HList, Out <: HList] {
def apply(x: X, y: Y): Out
}
object LiftA2 {
implicit def hnil[HF, Y <: HList] = new LiftA2[HF, HNil, Y, HNil] {
def apply(x: HNil, y: Y) = HNil
}
implicit def hlist[
HF, XH, XT <: HList, Y <: HList,
Out1 <: HList, Out2 <: HList, Out <: HList
](implicit
am: ApplyMapper[HF, XH, Y, Out1],
lift: LiftA2[HF, XT, Y, Out2],
prepend : PrependAux[Out1, Out2, Out]
) = new LiftA2[HF, XH :: XT, Y, Out] {
def apply(x: XH :: XT, y: Y) = prepend(am(x.head, y), lift(x.tail, y))
}
}
And finally our method itself:
def liftA2[HF, X <: HList, Y <: HList, Out <: HList](hf: HF)(x: X, y: Y)(implicit
lift: LiftA2[HF, X, Y, Out]
) = lift(x, y)
And that's all—now liftA2(tuple)(xs, ys) works.
scala> type Result =
| (Int, Double) :: (Int, String) ::
| (Symbol, Double) :: (Symbol, String) ::
| (Char, Double) :: (Char, String) :: HNil
defined type alias Result
scala> val res: Result = liftA2(tuple)(xs, ys)
res: Result = (1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Just as we wanted.