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.
Related
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)
I am trying to write a function that given an arbitrary case class replaces the value of the first field with the given new value, using Shapeless.
So far I have the following:
def replaceHead[T, H, R >: H :: HList](obj: T, newHead: H)(implicit generic: Generic.Aux[T, R]): T =
generic.to(obj) match {
case h :: t => generic.from(newHead :: t)
}
If I don't put any type restrictions on R, I cannot use generic.from(newHead :: t) because it understandably expects a subtype of R. If I put R >: H :: HList, I get the following error:
Error:(19, 22) could not find implicit value for parameter generic: shapeless.Generic.Aux[Test.Record,String :: shapeless.HList]
println(replaceHead(Record("abc", 123), "def"))
The only way that I could get it to "work" is with an ugly hack like this that is not type safe:
def replaceHead[T, H, R](obj: T, newHead: H)(implicit generic: Generic.Aux[T, R]): T =
generic.to(obj) match {
case h :: t => generic.from((newHead :: t).asInstanceOf[R])
}
replaceHead(Record("abc", 123), "def") // Works
replaceHead(Record("abc", 123), 456) // Crashes at runtime
I understand that the root problem is because R ends up being for instance String :: Int :: HNil which is String :: HList but String :: HList is not necessarily String :: Int :: HNil, but I can't find a way to access the head of the generic representation without erasing the type signature of the tail.
The following should do the trick,
def replaceHead[C, R <: HList, H, T <: HList](c: C, newHead: H)
(implicit
gen: Generic.Aux[C, R],
ev1: (H :: T) =:= R,
ev2: R =:= (H :: T)
): C = gen.from(newHead :: gen.to(c).tail)
scala> replaceHead(Foo(23, "foo"), 13)
res0: Foo = Foo(13,foo)
We need to two type equalities to prove that the representation type R is same as H :: T in both directions.
This is a good question and one I'm not convinced I've found the simplest solution for. But this does work:
trait NonEmptyHList[T, Head] {
def replaceHead(t: T, h: Head): T
}
implicit def nonEmptyHList[H, Rest <: HList]: NonEmptyHList[H :: Rest, H] = new NonEmptyHList[H :: Rest, H] {
def replaceHead(t: H :: Rest, h: H): H :: Rest = t match {
case _ :: rest => h :: rest
}
}
def replaceHead[T, H, R](obj: T, h: H)(implicit generic: Generic.Aux[T, R], nonEmpty: NonEmptyHList[R, H]): T = {
generic.from(nonEmpty.replaceHead(generic.to(obj), h))
}
val rec = new Record("hello", 1)
replaceHead(rec, "hi") // Record("hi", 1)
replaceHead(rec, 2) // compile error
My goal is to Wrap an HList and save enough information to perform prepend and split operations at a later time.
case class Wrap[L <: HList](wrapped: L)
val x = Wrap("x" :: "y" :: HNil)
val y = Wrap(1 :: 2 :: HNil)
case class Append[L1, L2](w1: Wrap[L1], w2: Wrap[L2], prepend: Prepend[L1, L2], length: Length[L1])
def append[L1, L2](w1: Wrap[L1], w2: Wrap[L2])(implicit prepend: Prepend[L1, L2], length: Length[L1]) = Append(w1, w2, prepend, length)
val xAppendY = append(x,y)
val merged = xAppendY.prepend(xAppendY.w1.wrapped, xAppendY.w2.wrapped)
val split = Split[xAppendY.prepend.Out, xAppendY.length.Out] // <-- error here
split.apply(merged)
This code fails with the implicit not found error:
Implicit not found: shapeless.Ops.Split[xAppendY.prepend.Out, xAppendY.length.Out]. You requested to split at position xAppendY.length.Out, but the HList xAppendY.prepend.Out is too short.
But it seems the the compiler should know that the types are String :: String :: String :: String :: HNil and Nat._2. Is there something that I need to do to help the compiler out here?
The following version of code works:
import shapeless.ops.hlist.{Length, Prepend, Split}
import shapeless.{::, HList, HNil, Nat}
case class Wrap[L <: HList](wrapped: L)
val x = Wrap("x" :: "y" :: HNil)
val y = Wrap(1 :: 2 :: HNil)
case class Append[L1 <: HList, L2 <: HList, L3 <: HList, N <: Nat](w1: Wrap[L1], w2: Wrap[L2], prepend: Prepend.Aux[L1, L2, L3], length: Length.Aux[L1, N])
def append[L1 <: HList, L2 <: HList, L3 <: HList, N <: Nat](w1: Wrap[L1], w2: Wrap[L2])(implicit prepend: Prepend.Aux[L1, L2, L3], length: Length.Aux[L1, N]) = Append(w1, w2, prepend, length)
val xAppendY = append(x,y)
val merged = xAppendY.prepend(xAppendY.w1.wrapped, xAppendY.w2.wrapped)
val split = Split[xAppendY.prepend.Out, xAppendY.length.Out]
split.apply(merged)
In your version of code xAppendY.prepend was of type Prepend[L1, L2] = Prepend[L1, L2] { type Out } rather than Prepend.Aux[L1, L2, L3] = Prepend[L1, L2] { type Out = L3 } for proper L3 and xAppendY.length was of type Length[L1] = Length[L1] { type Out } rather than Length.Aux[L1, N] = Length[L1] { type Out = N } for proper N.
Why do we need to specify a refined type (or its equivalent Aux) for the output of certain type computations?
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
I would like to create the equivalent of:
def toTupleN[A1, ..., AN, L <: HList](l: L): TupleN[A1, ..., AN]
Code using toTupleN should compile only when there is exactly one N combination of values in l that the tuple could be created from. Anything else should generate a compile-time error. Available implicit conversions should be taken into account. Note that there are no restrictions on the size of l or the ordering of values in it.
Example:
val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
// l: shapeless.::[Int,shapeless.::[(Int, String),shapeless.::[(Int, String),shapeless.::[String,shapeless.HNil]]]] = 23 :: (1,wibble) :: (2,wobble) :: foo :: HNil
val t2: (String, Int) = toTuple2(l)
// t2: (String, Int) = (foo,23)
val nope: (String, String) = toTuple2(l)
// Compiler error because no combination of l's values can create nope
val nein: ((Int, String)) = toTuple2(l)
// Another compiler error because there is more than one way l's values can create nein
This question arose from the answer to the following question. The more general machinery in this question could be used to both create data structures and call any standard function (whose arguments are of different types) using FunctionN#tupled.
Update:
Some examples to define the desired behavior with subtypes:
trait A
trait B extends A
trait C extends A
val a: A
val b: B
val c: C
toTuple2[(A, Int)](5 :: b :: HNil) // (b, 5): subtypes match supertypes when there is no exact match
toTuple2[(A, Int)](5 :: b :: a :: HNil) // (a, 5): only one exact match is available
toTuple2[(A, Int)](5 :: a :: a :: HNil) // compile error: more than one exact match is available
toTuple2[(A, Int)](5 :: b :: c :: HNil) // compile error: more than one inexact match is available
I haven't been able to make target type inference work out quite the way you wanted, but as compensation I've generalized to an arbitrary product type via shapeless's Generic,
import shapeless._, ops.hlist._, test._
object Demo {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hcons[L <: HList, H, T <: HList, S <: HList]
(implicit
pt: Partition.Aux[L, H, H :: HNil, S],
ust: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(l)
h :: ust(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
gen.from(up(l))
}
val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
val t2 = toProductUniquely[(String, Int)](l)
typed[(String, Int)](t2)
assert(t2 == ("foo", 23))
illTyped("""
toProductUniquely[(String, String)](l)
""")
illTyped("""
toProductUniquely[Tuple1[(Int, String)]](l)
""")
}
Update 1
Adding support for the selection being satisfied by subtypes of the requested types is fairly straightforward if we say that where we have types A and B <: A then the selection of A from A :: B :: HNil is ambiguous because both elements conform to A. This can be done by adding a SubtypeUnifier to the witnesses in the previous definition of hcons,
import shapeless._, ops.hlist._, test._
object Demo extends App {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hcons[L <: HList, M <: HList, H, T <: HList, S <: HList]
(implicit
su: SubtypeUnifier.Aux[L, H, M],
pt: Partition.Aux[M, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(su(l))
h :: upt(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
gen.from(up(l))
}
class A
class B extends A
class C
val ac = new A :: new C :: HNil
val bc = new B :: new C :: HNil
val abc = new A :: new B :: new C :: HNil
// Exact match
val tac = toProductUniquely[(A, C)](ac)
typed[(A, C)](tac)
// Subtype
val tbc = toProductUniquely[(A, C)](bc)
typed[(A, C)](tbc)
// Exact match again
val tabc = toProductUniquely[(B, C)](abc)
typed[(B, C)](tabc)
// Ambiguous due to both elements conforming to A
illTyped("""
toProductUniquely[(A, C)](abc)
""")
}
Update 2
We can also accommodate a unification semantics which gives preference to exact match and then falls back to a unique subtype as described in your updated question. We do this by combining the instances from the two solutions above: the exact match instance from the first at normal priority and the subtype match instance at low priority,
import shapeless._, ops.hlist._, test._
object Demo extends App {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect extends UniqueSelect0 {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hconsExact[L <: HList, H, T <: HList, S <: HList]
(implicit
pt: Partition.Aux[L, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(l)
h :: upt(s)
}
}
}
trait UniqueSelect0 {
implicit def hconsSubtype[L <: HList, M <: HList, H, T <: HList, S <: HList]
(implicit
su: SubtypeUnifier.Aux[L, H, M],
pt: Partition.Aux[M, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(su(l))
h :: upt(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P = gen.from(up(l))
}
trait A
trait B extends A
trait C extends A
val a: A = new A {}
val b: B = new B {}
val c: C = new C {}
// (b, 5): subtypes match supertypes when there is no exact match
toProductUniquely[(A, Int)](5 :: b :: HNil)
// (a, 5): only one exact match is available
toProductUniquely[(A, Int)](5 :: b :: a :: HNil)
// compile error: more than one exact match is available
illTyped("""
toProductUniquely[(A, Int)](5 :: a :: a :: HNil)
""")
// compile error: more than one inexact match is available
illTyped("""
toProductUniquely[(A, Int)](5 :: b :: c :: HNil)
""")
}