I'm trying to write a combinator for the scodec library that converts a Codec[K]
in to a Codec[L]
where K
is an HList
and L
is the equivalent HList
with all Unit
elements removed.
Implementing decoding can be done by decoding a K
and then filtering out all Unit
elements. Filtering out Unit
elements is directly supported by shapeless using filterNot
, which makes this trivial to implement.
Implementing encoding is accomplished by converting an L
to a K
, inserting ()
at the appropriate indices, and then delegating to the original Codec[K]
. I'm having trouble implementing the L => K
conversion though.
def dropUnits[K <: HList, L <: HList](codec: Codec[K])(
implicit fltr: FilterNot.Aux[K, Unit, L]): Codec[L] = new Codec[L] {
override def decode(buffer: BitVector) =
codec.decode(buffer).map { case (rest, l) => (rest, l.filterNot[Unit]) }
override def encode(xs: L) = {
???
}
}
I've tried a few different solutions without luck. Is this possible with shapeless?
I don't see a way to do this without a custom type class, but that approach isn't too bad:
import shapeless._
trait ReUnit[L <: HList, K <: HList] { def apply(l: L): K }
object ReUnit {
implicit object hnilReUnit extends ReUnit[HNil, HNil] {
def apply(l: HNil): HNil = HNil
}
implicit def hlistReUnit[H, L <: HList, K <: HList]
(implicit ru: ReUnit[L, K]): ReUnit[H :: L, H :: K] =
new ReUnit[H :: L, H :: K] {
def apply(l: H :: L): H :: K = l.head :: ru(l.tail)
}
implicit def unitReUnit[L <: HList, K <: HList]
(implicit ru: ReUnit[L, K]): ReUnit[L, Unit :: K] =
new ReUnit[L, Unit :: K] {
def apply(l: L): Unit :: K = () :: ru(l)
}
}
def reUnit[L <: HList, K <: HList](l: L)(implicit ru: ReUnit[L, K]) = ru(l)
And then:
scala> type Input = Int :: String :: HNil
defined type alias Input
scala> type WithUnits = Int :: Unit :: String :: Unit :: Unit :: HNil
defined type alias WithUnits
scala> reUnit[Input, WithUnits](1 :: "foo" :: HNil)
res0: WithUnits = 1 :: () :: foo :: () :: () :: HNil
Or in your context:
def dropUnits[K <: HList, L <: HList](codec: Codec[K])(implicit
fn: FilterNot.Aux[K, Unit, L]
ru: ReUnit[L, K]
): Codec[L] = new Codec[L] {
override def decode(buffer: BitVector) =
codec.decode(buffer).map { case (rest, l) => (rest, l.filterNot[Unit]) }
override def encode(xs: L) = codec.encode(ru(xs))
}
I didn't try compiling this dropUnits
but it should work.
The trick above is just to work toward the instance you want inductively. The base case is converting an HNil
to an HNil
. Then if you know how to convert an X
to a Y
, you also know how to do two things: convert an A :: X
to an A :: Y
and convert an X
to a Unit :: Y
. And that's all you need!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With