I have following product type implementation in shapeless:
trait CsvEncoder[A] {
def encode(value: A): List[String]
}
implicit val hnilEncoder: CsvEncoder[HNil] =
createEncoder(_ => Nil)
implicit def hlistEncoder[H, T <: HList](
implicit
hEncoder: CsvEncoder[H],
tEncoder: CsvEncoder[T]
): CsvEncoder[H :: T] =
createEncoder {
case h :: t =>
hEncoder.encode(h) ++ tEncoder.encode(t)
}
and it is used as follow:
val reprEncoder: CsvEncoder[String :: Int :: Boolean :: HNil] =
implicitly
println(reprEncoder.encode("abc" :: 123 :: true :: HNil))
Looking at the instance implementation of HList, I can not see the recursive call anywhere. Because HList is like a list, it should process recursively but I can not configure it out where that happened.
It happened in tEncoder.encode(t). In particular let's analyze the following code:
case h :: t =>
hEncoder.encode(h) ++ tEncoder.encode(t)
case h :: t deconstructs the HList in its head and tail parts. Then hEncoder.encode(h) converts the value h of type H into a List[String]. Afterwards tEncoder.encode(t) recursively encode the remaining HList until it reaches the base case represented by HNil for which the result is Nil, that is the empty List. The intermediate results are concatenated using ++ which is the operator used to concatenate two Scala List instances.
Related
I'm trying to use shapeless to do additions and removals from Hlists. But I can't seem to get it to work.
So I here are my lists:
object ShapelessExample {
import shapeless._
def main(args: Array[String]): Unit = {
case class Container[T <: Singleton](name: T)
val a = Container("A") :: Container("B") :: Container("C") :: HNil
val b = Container("B") :: Container("C") :: HNil
println {
a.removeAll[b.type] //doesn't work
}
}
}
So the removeAll method on Hlist only takes a type parameter, but I can't seem to use b.type. I can manually specify a.removeAll[Container["B"] :: Container["C"] :: HNil], but is there any way to just use b's type?
Shapeless tries to remove precisely type b.type but can't find it among Container["A"] :: Container["B"] :: Container["C"] :: HNil so #user is correct, singleton type b.type is too specific.
In order to infer an HList type from a val singleton type try to modify the method
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAll[L1 <: HList](b: L1)(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
a.removeAll(b) // (Container(B) :: Container(C) :: HNil,Container(A) :: HNil)
or
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAllFrom[O <: Singleton] = new {
def apply[L1 >: O <: HList]()(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
}
a.removeAllFrom[b.type]() //(Container(B) :: Container(C) :: HNil,Container(A) :: HNil)
I'm new to Shapless.
I'm trying to write a function that would take an HList of sequences of different types, convert it to a Seq[HList] containing the cartesian product of the original HList's elements, and iterate over the resulting Sequence
For example:
val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil
cartesianProduct: Seq[Boolean :: Int :: String :: HNil] = Seq(
true :: 1 :: foo :: HNil,
true :: 1 :: bar :: HNil,
true :: 2 :: foo :: HNil,
true :: 2 :: bar :: HNil,
true :: 3 :: foo :: HNil,
true :: 3 :: bar :: HNil,
false :: 1 :: foo :: HNil,
false :: 1 :: bar :: HNil,
false :: 2 :: foo :: HNil,
false :: 2 :: bar :: HNil,
false :: 3 :: foo :: HNil,
false :: 3 :: bar :: HNil)
I was able to achieve this in Intellij Scala Worksheet using the following code:
import shapeless._
import shapeless.ops.hlist.LeftFolder
object combine extends Poly {
implicit def `case`[T <: HList, S] = use((acc : Seq[T], curr : Seq[S]) => {
for {
el <- curr
v <- acc
} yield el :: v
})
}
val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil
val combinations = input.foldLeft(Seq[HNil](HNil))(combine)
combinations.foreach(println)
Here everything works, I assume, because the full type of input is known to the compiler.
However, when I try to wrap the whole operation in a function, the full type of input gets lost, and I can't call foreach on the result of foldLeft:
def cartesian[T <: HList](input: T)
(implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
.foreach(println)
}
The compiler complains:
value foreach is not a member of folder.Out
input.foldLeft(Seq[HNil](HNil))(combine).foreach(println)
^
I imagine there is some implicit evidence I can request to assert the correct shape of input (HList of Seq[_]) and thus get the compiler to figure out the resulting type of the foldLeft, but I can't figure out what it could be...
Hope someone can help me figure this out.
Thanks.
Update:
My eventual goal with this question was, given an HList of Seq[_] to derive a function (perhaps on a case class) that will accept a function with the same arg arity as the input HList and the argument types matching the 'Seq' element types in the same order. For example for the input above the function would be f: (Boolean, Int, String) => R
So in affect I would be able to iterate over the cartesian product of the input using f.
The final code looks like this:
import shapeless._
import shapeless.ops.function.FnToProduct
import shapeless.ops.hlist.LeftFolder
import shapeless.syntax.std.function.fnHListOps
object combine extends Poly {
implicit def `case`[EL <: HList, S] = use((acc : Seq[EL], curr : Seq[S]) => {
for {
el <- curr
v <- acc
} yield el :: v
})
}
case class Cartesian[R <: HList, F, FR](combinations: Seq[R])
(implicit ftp: FnToProduct.Aux[F, R => Unit]) {
def foreach(f: F) = combinations.foreach(f.toProduct)
}
def cartesian[T <: HList, R <: HList, F, FR](variants: T)(implicit
folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[R]],
fnToProd: FnToProduct.Aux[F, R => Unit]
) = {
val combinations: Seq[R] = variants.foldLeft(Seq[HNil](HNil))(combine)
Cartesian(combinations)
}
val variants = Seq(true, false) :: Seq("foo", "bar") :: Seq(1, 2, 3) :: HNil
cartesian(variants).foreach((a, b, c) => println(s"$a, $b, $c"))
Note that the types for the function arguments a, b, c are correctly inferred and are Boolean, String, and Int.
Currently the result type of the function passed into foreach has to be fixed (in the code above it is Unit). It can't be inferred from the function passed in.
The problem is not that compiler doesn't know anything about input, but rather that it does not know anything about output.
Inside def cartesian all that is known to compiler is that after foldLeft you get some type folder.Out (which depends on an instance that shapeless would figure for you).
For ensuring result types, you can use LeftFolder.Aux with one extra type parameter, e.g.
def cartesian[T <: HList](input: T)
(implicit folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[Any]]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
.foreach(println)
}
Now the compiler will know that result is some subtype of Seq[Any], so it is possible to call foreach on it.
Of course, this is only the problem inside the def. At call sites output types will be resolved based on input, so you would be able to do this without Aux:
def cartesian2[T <: HList](input: T)
(implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
}
cartesian2(input).foreach(println)
Runnable code: https://scalafiddle.io/sf/n409yNW/2
Basically I want to transpose my object. Is there a simple way to do this? If my HList is large and I don't want to fold.
This is in service of unzipping a List of a high-arity tuples.
I don't know of an existing solution, but it seemed to be an interesting task, so I tried my hand at it.
Assuming we have a list of HLists, like
val myList: List[Int :: String :: Boolean :: HNil] = List(
1 :: "sample1" :: true :: HNil,
2 :: "sample2" :: false :: HNil,
3 :: "sample3" :: true :: HNil
)
and we want a HList of Lists, like
val expected: List[Int] :: List[String] :: List[Boolean] :: HNil =
List(3, 2, 1) ::
List("sample3", "sample2", "sample") ::
List(true, false, true) ::
HNil
we need to create one typeclass. I named it EmptyOf. It has a single method that creates an HList containing empty lists.
trait EmptyOf[T <: HList, AsList <: HList] {
def empty: AsList
}
Here is the implementation for HNil:
implicit val emptyOfHNil: EmptyOf[HNil, HNil] = () => HNil
And now for HCons:
implicit def emptyOfHCons[H, T <: HList, TEmpty <: HList](
implicit ev: EmptyOf[T, TEmpty]
): EmptyOf[H :: T, List[H] :: TEmpty] = () => List.empty[H] :: ev.empty()
So, for EmptyOf[T :: U :: HNil, List[T] :: List[U] :: HNil] our instance's empty will return Nil :: Nil :: HNil.
With these, we can implement transpose by folding on the list of HLists and using a Poly2 to merge the HLists.
object concat extends Poly2 {
implicit def prepend[S] = at[List[S], S]((list, s) => s :: list)
}
def transpose[T <: HList, U <: HList](lists: List[T])
(implicit emptyOf: EmptyOf[T, U],
zip: ZipWith.Aux[U, T, concat.type, U]): U =
lists.foldLeft(emptyOf.empty())(_.zipWith(_)(concat))
Now transpose(myList) should be equal to expected (and of the correct type).
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"
}
Looking at Travis Brown's excellent blog post on Type classes and generic derivation, I see the following method:
implicit def hconsParser[H: Parser, T <: HList: Parser]: Parser[H :: T] =
new Parser[H :: T] {
def apply(s: String): Option[H :: T] = s.split(",").toList match {
case cell +: rest => for {
head <- implicitly[Parser[H]].apply(cell)
tail <- implicitly[Parser[T]].apply(rest.mkString(","))
} yield head :: tail
}
}
What's the meaning of H :: T in Parser[H :: T]?
Also, how does this case cell +: rest handle the case where s, i.e. input to apply is empty?
H :: T is the infix form of the type ::[H, T], which is an HList with a head of type H and tail with type T <: HList. i.e. we're looking for a Parser for the type ::[H, T].
The infix usage is achieved like this, where infix can be any name:
scala> trait infix[A, B]
scala> def test[A, B](ab: A infix B) = ???
test: [A, B](ab: infix[A,B])Nothing
Also, how does this case cell +: rest handle the case where s, i.e. input to apply is empty?
If s is an empty string, then s.split(",").toList will simply be a List with an empty string as its single element. case cell +: rest would then never run into an empty list.