Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shapeless: Inversion of filterNot on an HList

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?

like image 523
mpilquist Avatar asked Aug 30 '14 03:08

mpilquist


1 Answers

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!

like image 93
Travis Brown Avatar answered Oct 23 '22 00:10

Travis Brown