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.
Related
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
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
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.
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"
}
Question
Is it somehow possible to create an extractor for shapeless' HList that looks like the following.
val a ~ _ ~ b = 4 :: "so" :: 4.5 :: HNil
=> a == 4 && b == 4.5
Replace :: by ~, which shouldn't be the problem.
Get rid of the terminating HNil. Are there any problems that might arise?
Motivation
After much sweat and tears I managed to arrive at the point, where the following code works:
for(
x1 :: _ :: x2 :: HNil <- (expInt ~ "+" ~ expInt).llE
) yield (x1 + x2)
expInt parses an Int in some monad E. The type of (expInt ~ "+" ~ expInt).llE is E[Int :: String :: Int :: HNil].
I want the pattern on the left of the <- to somehow resemble the construction of the combinator parser on the right.
This can be done, and has a couple of interesting twists.
The first is that, typically, for matching a structure that's built with a right associative constructor (ie. ::) you would use a correspondingly right associative extractor, otherwise you would decompose and bind the extracted elements in reverse order. Unfortunately right associative extractors must, like right associative operators, end with a : in Scala and that would clobber your parser combinator syntax since the extractor name would have to be ~: instead of plain ~. However, I'll postpone this for the moment and work with right associativity.
The second twist is that we need the unapply method to yield results of different types depending on whether we're matching an HList of more than two elements or of exactly two elements (and we shouldn't be able to match a list of less that two elements at all).
If we're matching a list of more than two elements we need to decompose the list into a pair consisting of the head and the HList tail, ie. given l: H :: T where T <: HList we must yield a value of type (H, T). If, on the other hand, we're matching a list of exactly two elements, ie. of the form E1 :: E2 :: HNil, we need to decompose the list into a pair consisting of just those two elements, ie (E1, E2) rather than a head and a tail which would be (E1, E2 :: HNil).
This can be done using exactly the same type level programming techniques that are used throughout shapeless. First we define a type class which is going to do the work of the extractor, with instances corresponding to each of the two cases described above,
import shapeless._
trait UnapplyRight[L <: HList] extends DepFn1[L]
trait LPUnapplyRight {
type Aux[L <: HList, Out0] = UnapplyRight[L] { type Out = Out0 }
implicit def unapplyHCons[H, T <: HList]: Aux[H :: T, Option[(H, T)]] =
new UnapplyRight[H :: T] {
type Out = Option[(H, T)]
def apply(l: H :: T): Out = Option((l.head, l.tail))
}
}
object UnapplyRight extends LPUnapplyRight {
implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
new UnapplyRight[H1 :: H2 :: HNil] {
type Out = Option[(H1, H2)]
def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
}
}
Then we define our extractor in terms of it like so,
object ~: {
def unapply[L <: HList, Out](l: L)
(implicit ua: UnapplyRight.Aux[L, Out]): Out = ua(l)
}
And then we're good to go,
val l = 23 :: "foo" :: true :: HNil
val a ~: b ~: c = l
a : Int
b : String
c : Boolean
So far, so good. Now let's return to the associativity issue. If we want to achieve the same effect with a left associative extractor (ie. ~ instead of ~:) we'll need to change the way the decomposition is done. First let's desugar the right associative extractor syntax we just used. The expression,
val a ~: b ~: c = l
is equivalent to,
val ~:(a, ~:(b, c)) = l
By contrast, the left associative version,
val a ~ b ~ c = l
is equivalent to,
val ~(~(a, b), c) = l
To make this work as an extractor for HLists our unapply type class must peel elements off from the end, rather from beginning of the list. We can do this with the aid of shapeless's Init and Last type classes,
trait UnapplyLeft[L <: HList] extends DepFn1[L]
trait LPUnapplyLeft {
import ops.hlist.{ Init, Last }
type Aux[L <: HList, Out0] = UnapplyLeft[L] { type Out = Out0 }
implicit def unapplyHCons[L <: HList, I <: HList, F]
(implicit
init: Init.Aux[L, I],
last: Last.Aux[L, F]): Aux[L, Option[(I, F)]] =
new UnapplyLeft[L] {
type Out = Option[(I, F)]
def apply(l: L): Out = Option((l.init, l.last))
}
}
object UnapplyLeft extends LPUnapplyLeft {
implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
new UnapplyLeft[H1 :: H2 :: HNil] {
type Out = Option[(H1, H2)]
def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
}
}
object ~ {
def unapply[L <: HList, Out](l: L)
(implicit ua: UnapplyLeft.Aux[L, Out]): Out = ua(l)
}
And now we're done,
val a ~ b ~ c = l
a : Int
b : String
c : Boolean