How to flatten an HLists of HLists - scala

So at runtime I get an Hlist of Hlists which looks like:
(2 :: HNil) :: (1001 :: HNil) :: (1001 :: HNil) :: HNil
Here the type of the resultant Hlist is :
(Int :: HNil) :: (Long :: HNil) :: (Long :: HNil) :: HNil
but this could have been any other type at runtime. For example, it could have been
(Int :: HNil) :: (String :: HNil) :: HNil
Is there a way to flatten this HList like so:
2 :: 1001 :: 1001 :: HNil
with the following type:
Int :: Long :: Long :: HNil

You can do it applying functional operations to HList. Take a look to how Poly works. First, we declare the Poly for (Int::HNil) and (Long::HNil):
import shapeless._
object myPoly extends Poly1 {
implicit val tupleIntCase: Case.Aux[(Int :: HNil), Int] =
at(position => position.head)
implicit val tupleLongCase: Case.Aux[(Long :: HNil), Long] =
at(position => position.head)
}
and using the map function you can extract and flatten the HList:
((2 :: HNil) :: (1001L :: HNil) :: (1001 :: HNil) :: HNil).map(myPoly)
It gives the desired result:
2 :: 1001 :: 1001 :: HNil
Note that the resulting type here is:
Int :: Long :: Int :: HNil

You can use the identity Poly defined by shapeless for conciseness:
import shapeless._
import shapeless.poly.identity
val nestedHList = (2 :: HNil) :: (1001 :: HNil) :: (1001 :: HNil) :: HNil
nestedHList.flatMap(identity)
// 2 :: 1001 :: 1001 :: HNil

Related

How to implement HList literal type constraints?

I'm trying to do some calculations on HList types (to enforce a tensor algebra). I'm failing so far, using 2.13.0-M4.
Here is the challenge:
type XInt = Int with Singleton
def mult[N <: XInt, M <: XInt, P <: XInt, T <: HList]
(a: N :: M :: T, b: M :: P :: T): N :: P :: T= ???
val a = 8.narrow :: 4.narrow :: HNil
val b = 4.narrow :: 5.narrow :: HNil
mult(a, b) // should yield 8 :: 5 :: HNil
Try
def mult[N <: XInt, M <: XInt, P <: XInt, T <: HList](a: N :: M :: T, b: M :: P :: T): N :: P :: T =
(a, b) match { case (n :: _ :: t, _ :: p :: _) => n :: p :: t }
or
def mult[N <: XInt, M <: XInt, P <: XInt, T <: HList](a: N :: M :: T, b: M :: P :: T): N :: P :: T =
a.head :: b.tail.head :: b.tail.tail
or simply
def mult[N <: XInt, M <: XInt, P <: XInt, T <: HList](a: N :: M :: T, b: M :: P :: T): N :: P :: T =
a.head :: b.tail

retain key information in implicit LabelledGeneric

I have this code that convert an arbitrary size of class to an HList
class Record[H <: HList](val hs: H)
object Record {
import shapeless.LabelledGeneric
def apply[P <: Product, L <: HList](p: P)(implicit gen: LabelledGeneric.Aux[P, L]) = new Record[L](gen.to(p))
}
I would like to use it as follows:
import shapeless._
import syntax.singleton._
val h = Record('a ->> 1, 'b ->> 2)
# h.hs
res91: cmd90.<refinement>.this.type.Out = 1 :: 2 :: HNil
However the keys are not retained after the conversion. As opposed to creating the same HList:
# h.hs.keys //error: could not find implicit value for parameter keys: shapeless.ops.record.Keys[this.Out]
# ('a ->> 1 :: 'b ->> 2 :: HNil).keys //works ok
The question is how to retain the key information in the record.
You use records incorrectly.
Things like 'a ->> 1, 'b ->> 2 shouldn't be used separately, they should be used inside HLists like 'a ->> 1 :: 'b ->> 2 :: HNil.
When you write Record('a ->> 1, 'b ->> 2) the argument of Record is considered as Tuple2. Since Tuple2 is a case class, LabelledGeneric transforms it implicitly into an HList (actually, a shapeless' record). Look at your h, its type is very strange (pseudocode):
Record['_1 -> 'a -> Int :: '_2 -> 'b -> Int :: HNil]
(_1 and _2 are field names of Tuple2).
So how to fix your code depends on what you really wanted to get (I'm not sure). One possible way is
// class Record[H <: HList](val hs: H)
object Record {
def apply[P <: Product, L <: HList](p: P)(implicit gen: LabelledGeneric.Aux[P, L]) = /*new Record[L](*/gen.to(p)/*)*/
}
case class MyCaseClass(a: Int, b: Int)
val h = Record(MyCaseClass(1, 2))
h/*.hs*/ // 1 :: 2 :: HNil
h/*.hs*/.keys // 'a :: 'b :: HNil
('a ->> 1 :: 'b ->> 2 :: HNil).keys // 'a :: 'b :: HNil
I would like to have something like Record('a ->> 1, 'b ->> 2) ... or potentially Record(('a', 1), ('b', 2))
as an input, and an HList 'a ->> 1 :: 'b ->> 2 :: HNil as an output.
Then you don't need LabelledGeneric. You can transform Record('a ->> 1, 'b ->> 2) into just a tuple ('a ->> 1, 'b ->> 2) (it's Record.unapply(...).get if you defined case class Record(p: Product)) and then this tuple into HList:
import shapeless.{::, HNil}
import shapeless.syntax.std.tuple._
import shapeless.syntax.singleton._
('a ->> 1, 'b ->> 2).productElements == 'a ->> 1 :: 'b ->> 2 :: HNil // true

Scala can not resolve append

I want to remove all instances of an element from a list, like this:
def remove(x: Char, xs: List[Char]) = xs match {
case Nil => Nil
case x :: ys => remove(x, ys)
case y :: ys => y :: remove(x, ys)
}
However, I get an error on the line case y :: ys => y :: remove(x, ys) saying Cannot resolve symbol ::
Am I pattern matching correctly?
I got a couple of different compile errors, first: "error: recursive method remove needs result type" (fixed by adding return type of List[Char]), then (as I expected): "warning: unreachable code" on the line with y :: ys. This latter warning comes because the x used in the line with case x :: ys is not recognised as the same x from the function's arguments - it is shadowing the argument variable, and so matched any character.
To be sure to match the function argument, add backticks:
def remove(x: Char, xs: List[Char]): List[Char] = xs match {
case Nil => Nil
case `x` :: ys => remove(x, ys)
case y :: ys => y :: remove(x, ys)
}
or you can capitalise the argument name (capitalised variables in a match case are treated specially this way):
def remove(X: Char, xs: List[Char]): List[Char] = xs match {
case Nil => Nil
case X :: ys => remove(X, ys)
case y :: ys => y :: remove(X, ys)
}
If using this style, don't forget to capitalise the argument everywhere it is referenced!
You need to specify the return type if you are using recursive call. This will work:
def remove(x: Char, xs: List[Char]):List[Char] = xs match {
case Nil => Nil
case x :: ys => remove(x, ys)
case y :: ys => y :: remove(x, ys)
}
I agree, the error is misleading
The first problem is that a recursive method needs to declare its return type.
def remove(x: Char, xs: List[Char]): List[Char] = xs match {
case Nil => Nil
case x :: ys => remove(x, ys)
case y :: ys => y :: remove(x, ys)
}
The second problem is that both cases are the same so the second one is unreachable.

Splitting an HList that was concatenated using Prepend[A, B]

I'm essentially looking for the opposite of the type class Prepend[A, B].
If I have something like:
type A = String :: Int :: HNil
type B = Boolean :: Double :: HNil
val a: A = "a" :: 1 :: HNil
val b: B = false :: 2.1 :: HNil
scala> val ab = a ++ b
ab: shapeless.::[String,shapeless.::[Int,shapeless.::[Boolean,shapeless.::[Double,shapeless.HNil]]]] = a :: 1 :: false :: 2.1 :: HNil
I have an HList a of type A and an HList b of type B, I can find a prepend: Prepend[A, B] such that I can concatenate them with a ++ b.
But if I have an HList ab of type prepend.Out, how can I extract the original A and B? I can't seem to find a type class that does the job, and perhaps there isn't one. It seems like I would need something like trait Cut[A <: HList, B <: HList, c <: HList] that witnesses that C has been created by pre-pending A to B, though I'm not sure how I would go about generating witnesses.
Very roughly like:
def Cut[A <: HList, B <: HList, C <: HList](c: C)(implicit cut: Cut[A, B, C]): (A, B) = ???
You can do this fairly straightforwardly with Split:
import shapeless._, ops.hlist.{ Length, Prepend, Split }
class UndoPrependHelper[A <: HList, B <: HList, C <: HList, N <: Nat] {
def apply(c: C)(implicit split: Split.Aux[C, N, A, B]): (A, B) = split(c)
}
def undoPrepend[A <: HList, B <: HList](implicit
prepend: Prepend[A, B],
length: Length[A]
) = new UndoPrependHelper[A, B, prepend.Out, length.Out]
And then:
scala> type A = Int :: String :: Symbol :: HNil
defined type alias A
scala> type B = List[Int] :: Option[Double] :: HNil
defined type alias B
scala> type C = Int :: String :: Symbol :: List[Int] :: Option[Double] :: HNil
defined type alias C
scala> val a: A = 1 :: "foo" :: 'bar :: HNil
a: A = 1 :: foo :: 'bar :: HNil
scala> val b: B = List(1, 2, 3) :: Option(0.0) :: HNil
b: B = List(1, 2, 3) :: Some(0.0) :: HNil
scala> val c: C = a ++ b
c: C = 1 :: foo :: 'bar :: List(1, 2, 3) :: Some(0.0) :: HNil
scala> val (newA: A, newB: B) = undoPrepend[A, B].apply(c)
newA: A = 1 :: foo :: 'bar :: HNil
newB: B = List(1, 2, 3) :: Some(0.0) :: HNil
I recently added an "undo" operation for the Remove type class, and it might make sense to have something similar built into Prepend.

Sort elements as part of concatenating elements of 2 Lists

Here is a function I wrote for concatenating elements of a List using an accumulator with tail recursion :
val l1 = List(1, 2, 3) //> l1 : List[Int] = List(1, 2, 3)
val l2 = List(1, 2, 3) //> l2 : List[Int] = List(1, 2, 3)
def func(l1: List[Int], l2: List[Int], acc: List[Int]): List[Int] = {
(l1, l2) match {
case (Nil, Nil) => acc.reverse
case (h1 :: t1, h2 :: t2) => {
func(t1, t2, h1 :: h2 :: acc)
}
}
} //> func: (l1: List[Int], l2: List[Int], acc: List[Int])List[Int]
func(l1, l2, List()) //> res0: List[Int] = List(1, 1, 2, 2, 3, 3)
This is my understanding of the call order
func( 1 :: 1 :: () )
func( 2 :: 2 :: 1 :: 1 : () )
func( 3 :: 3 :: 2 :: 2 :: 1 :: 1 : () )
So the call order is the reason why I must call reverse on base call of acc so that the List is ordered in same ordering initial List elements. To try to minimize the steps required to concatenate the lists I have tried to add the elements like this :
func(t1, t2, acc :: h1 :: h2)
instead of
func(t1, t2, h1 :: h2 :: acc)
but receive compile time error :
value :: is not a member of Int
So it seems I cannot prepend these elements to this List ?
When you write x :: y, y must be a list and x the element you want to prepend.
You can use acc :+ h1 :+ h2 to append h1 and h2 to acc, but note that adding elements to the end of the list is a relatively expensive operation (linear with the length of the list).