Map the types of a shapeless HList
Asked Answered
K

1

7

I've been trying to map the types of an HList from scala's shapeless package without having access to their values.

The following succeeds in mapping the values of an HList

import shapeless._
import shapeless.Poly._
import ops.hlist.Mapper
import ops.hlist.Mapper._

trait Person {
  type Value
  val v : Value
}

case class StringPerson extends Person {
  type Value = String
  val v = "I like strings"
}

case class IntPerson extends Person {
  type Value = Int 
  val v = 42
}

object what_is_going_on {

  object test_value_op {
    val stringPerson = StringPerson()
    val intPerson = IntPerson()

    trait lpvfun extends Poly1 {
      implicit def default[A <: Person] = at[A](_.v)
    } 

    object vfun extends lpvfun {}

    // Use these to generate compiler errors if the mapped type is not what we'd expect:

    type TestListType = StringPerson :: IntPerson :: HNil
    type TestListExpectedMappedType = String :: Int :: HNil

    // Input:
    val testList : TestListType = stringPerson :: intPerson :: HNil

    // Output:
    val mappedList : TestListExpectedMappedType = testList map vfun

    // Get the actual mapped type 
    type TestListActualMappedType = mappedList.type

    // This compiles......
    val mappedList1 : TestListActualMappedType = mappedList

    // .... but weirdly this line doesn't. That isn't the point of this question, but I'd be very grateful for an answer.
    //implicitly[TestListActualMappedType =:= TestListExpectedMappedType]
  }

}

Cool! Apart from not being able to use implicitly[A =:= B] for some reason, the values of an HList have been mapped and so have their types.

Now, suppose we don't have the HList value but we know its type. How can we map over its types?

I tried the following based on the definition of map here:

object test_type_op { 
  type TestListType = StringPerson :: IntPerson :: HNil
  type TestListExpectedMappedType = String :: Int :: HNil

  // Attempt 1 does not work, compiler cannot prove =:=
  type MappedType = Mapper[vfun.type, TestListType]#Out
  implicitly[MappedType =:= TestListExpectedMappedType]

  // Attempt 2 does not work, compiler cannot prove =:=
  class GetMapper {
    implicit val mapper : Mapper[vfun.type, TestListType]
    implicitly[mapper.Out =:= TestListExpectedMappedType]
  }

}

How does one get the type of a mapped HList without having access to its value? Is there a way of debugging why the compiler can't prove something? Thankyou for reading.

Klausenburg answered 2/10, 2016 at 23:44 Comment(0)
H
4

In the case of TestListActualMappedType you've got the singleton type for mappedList, which isn't the same as the inferred type of mappedList. You can see exactly the same issue without involving Shapeless:

scala> val x = "foo"
x: String = foo

scala> implicitly[x.type =:= String]
<console>:13: error: Cannot prove that x.type =:= String.
       implicitly[x.type =:= String]
                 ^

You could ask for evidence that x.type is a subtype of String or you could use shapeless.test.typed, which would look like this in your case:

import shapeless._, ops.hlist.Mapper

trait Person {
  type Value
  val v : Value
}

case class StringPerson() extends Person {
  type Value = String
  val v = "I like strings"
}

case class IntPerson() extends Person {
  type Value = Int 
  val v = 42
}

trait lpvfun extends Poly1 {
  implicit def default[A <: Person] = at[A](_.v)
} 

object vfun extends lpvfun {}

val stringPerson = StringPerson()
val intPerson = IntPerson()

val testList = stringPerson :: intPerson :: HNil
val mappedList = testList map vfun

shapeless.test.typed[String :: Int :: HNil](mappedList)

This doesn't really buy you much over explicitly specifying the type, though.

You can ask for evidence that the output type of a type class like Mapper is the type you expect for particular input types:

scala> val m = Mapper[vfun.type, StringPerson :: IntPerson :: HNil]
m: shapeless.ops.hlist.Mapper[vfun.type,shapeless.::[StringPerson,shapeless.::[IntPerson,shapeless.HNil]]]{type Out = shapeless.::[String,shapeless.::[Int,shapeless.HNil]]} = shapeless.ops.hlist$Mapper$$anon$5@6f3598cd

scala> implicitly[m.Out =:= (String :: Int :: HNil)]
res1: =:=[m.Out,shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] = <function1>

This is more likely to be useful, but again it depends on what exactly you're trying to convince yourself of.

Hobbie answered 3/10, 2016 at 0:28 Comment(1)
Thankyou - this really helps. In your second code block, what happens to m at compile time? Is it optimized out? Is there any way to get the type that m.Out would have without actually creating any values?Klausenburg

© 2022 - 2024 — McMap. All rights reserved.