4
votes

This example is simplified.

I have a set of classes like this:

case class KeyMapping[KeyType](k:KeyType)

class WrappedMapping[KeyType](m:T forSome {type T <: KeyMapping[KeyType]}) {
  val k:KeyType = ???
}

In the following code the types are correctly inferred:

 val w = new WrappedMapping(KeyMapping("key"))

 //The statement below gives the correct error
 //type mismatch; 
 //  found : w.k.type (with underlying type String)  required: Nothing
 //val test1:Nothing = w.k

I have no clue how to infer the type correctly for the following:

class Mappings[KeyType, L <: HList](mappings:L) {
  val k:KeyType = ???
}

val m = new Mappings(KeyMapping("key1") :: KeyMapping("key2") :: HNil)

// should not compile, k should be of type String
val test2:Nothing = m.k

Is there a way I can infer the KeyType based on the contents of the HList?

1
Is there any reason why you're not using the signature class WrappedMapping[KeyType](m: KeyMapping[KeyType])? Right now, that existential type sticks out like a sore thumb, and it definitely makes the question a lot harder to answer.Kevin Wright
Actually, never mind. I see that the WrappedMapping isn't used in MappingsKevin Wright
Do you just want to establish that all the mappings in the HList have the same key type? The goal is a little unclear.Travis Brown
I want KeyType in Mappings to be inferred with the upper bound of the types of keys. If the only solution for the correct inference is to only allow one type of key, that is fine too. As long as it's inferred from the KeyMappings in the HList.EECOLOR

1 Answers

8
votes

Shapeless provides a ToList implicit which is used, unsurprisingly, to convert HLists to Lists.

In order to do that, it must first calculate the LUB (least upper bound) of the types in the HList, and that's something you can use:

import shapeless.ops.hlist.ToList

class Mappings[L <: HList, Lub](mappings:L)(implicit toList: ToList[L, Lub]) {
  ...
}

L goes in, implicit resolution discovers the one (and only) valid ToList instance constrained by that L, the Lub type of which is pulled out.

That's not enough though, as Lub will be KeyMapping[String], whereas all you want is the String part. As usual with shapeless, the solution is to add another implicit:

class Mappings[L <: HList, Lub, KeyType](mappings:L)(
  implicit
  val toList: ToList[L, Lub],
  val kt: Lub <:< KeyMapping[KeyType]
) {
  val k: KeyType = null.asInstanceOf[KeyType]
}

(the implicits don't need to be vals, but it helps if they are when you're exploring things in the REPL)

What this asserts is that Lub corresponds to the type KeyMapping[KeyType] (e.g. it's a subtype or the exact same type), where KeyType is, as yet, unknown. Again, there's only one valid solution to the constraints specified, and the KeyType parameter is pulled out as being String.

I have no idea how you plan on implementing k, but you may find that having that toList instance helps you to do so, as it allows you to now invoke mappings.toList