Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)  = ???
like image 359
Michael Zajac Avatar asked Sep 03 '15 03:09

Michael Zajac


1 Answers

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.

like image 146
Travis Brown Avatar answered Sep 20 '22 15:09

Travis Brown