Search code examples
scalashapelessdependent-typesingleton-type

Can shapeless Record type be used as a Poly1? - Part 2


Since I didn't get an answer from the Part 1:

Can shapeless Record type be used as a Poly1?

I assume this feature doesn't exist in shapeless. So I decided own my fate and to write one myself:

  import shapeless.record._

  case class GetV[H <: HList](hh: H) extends Poly1 {

    implicit def getter[S](
        implicit
        ev: Selector[H, S]
    ): Case.Aux[S, ev.Out] = at[S] { s =>
      val w = Witness(s)
      val _ev = ev.asInstanceOf[Selector[H, w.T]]

      val v = hh.apply(w)(_ev)
      v.asInstanceOf[ev.Out]
    }
  }

It works as anticipated, the only problems are 2 invocation of asInstanceOf which I consider to be an unsafe hack to circumvent typechecker's pitfall on singleton types. What should be done to improve it?

In case you want to know its capability, this is my test code:

  import shapeless.syntax.singleton._

  val record = ("a" ->> 1) ::
    ("b" ->> "x") ::
    HNil

  it("getV") {

    object get extends RecordUtils.GetV(record)

    assert(get.apply("a".narrow) == 1)
    assert(get("b".narrow) == "x")
  }

UPDATE 1: this is just one of all the problems I have observed, if I change the test case into something equivalent:

  it("getV") {

    // object get extends RecordUtils.GetV(record) <----- should be the same
    val get = RecordUtils.GetV(record)

    assert(get.apply("a".narrow) == 1)
    assert(get("b".narrow) == "x")
  }

It breaks the compilation:

[Error] .../RecordUtilsSpec.scala:19: could not find implicit value for parameter cse: shapeless.poly.Case[get.type,String("a") :: shapeless.HNil]
[Error] .../RecordUtilsSpec.scala:20: could not find implicit value for parameter cse: shapeless.poly.Case[get.type,String("b") :: shapeless.HNil]
two errors found

What's the difference between 2 test cases? Are val/object get both unstable paths so their dependent types have local scope?


Solution

  • Please see my answer for Part 1.

    Regarding Part 2, you can define implicit def getter without asInstanceOf much easier using type class that you already have as an implicit parameter (instead of extension method and Witness)

    case class GetV[H <: HList](hh: H) extends Poly1 {    
      implicit def getter[S](implicit
        ev: Selector[H, S]
      ): Case.Aux[S, ev.Out] = at[S] { _ =>
        ev(hh)
      }
    }
    

    Regarding update, it's common situation in Shapeless that polymorphic functions should be defined via objects rather than vals. Otherwise you have to import implicits

    val get = GetV(record)
    import get._