Search code examples
scalashapeless

How map a shapeless HList of Nat


I have a HList of Nat, and I want to map over it

object NatToString extends Poly1 {
    implicit def caseNat = at[Nat](_.toString)
}

val list = _5 :: _3 :: HNil
list.map(NatToString) 

This code doesn't compile and throw:

could not find implicit value for parameter mapper:
shapeless.ops.hlist.Mapper[Main.Nat_to_String.type,shapeless.::[shapeless.Nat._5,shapeless.::[shapeless.Nat._3,shapeless.HNil]]]

But if I do exactly the same thing with Int (or String, or List, etc.) in place of Nat, it works perfectly.

How can I map over an HList of Nat ?


Solution

  • The issue is that Poly1.Case is not covariant in its type parameter. Consider the following:

    trait Foo
    trait Bar extends Foo
    
    val foo = new Foo {}
    var bar = new Bar {}
    
    object fooIdentity extends Poly1 {
      implicit def caseFoo = at[Foo](identity)
    }
    

    Now fooIdentity(foo) will compile, but fooIdentity(bar) won't.

    In your case the members of the HList are statically typed as _5 and _3. These are subtypes of Nat, but NatToString doesn't care, since its only case is looking for something statically typed as Nat.

    The trick is just to add a type parameter to the case:

    object NatToString extends Poly1 {
      implicit def caseNat[N <: Nat] = at[N](_.toString)
    }
    

    You'll find that in general working with Nat directly isn't what you want—you'll almost always want a specific subtype.