scalatypestypeclassparameterized

How do I have the scala compiler infer one type from another?


I want to create a Scala Id typeclass, so that, for example, I can declare that an Id for type Foo takes a Long value, e.g.

val fooId: Id[Foo] = Id(12L) // type-safe at compile time
val fooIdValue: Long = fooId.value // able to get the value back out

I've tried various ways but I can't seem to enforce the constraints. If I declare

trait WithId[I] {
  type Id = I
}

case class Id[A <: WithId[_]](value: A#Id) // A#Id => Any, not what I want!

class Foo extends WithId[Long] {
  type Id = Long
}

this allows

val fooId: Id[Foo] = Id("foo") // should be illegal unless a Long

If I change WithId to use an abstract type

trait WithId {
  type Id
}

case class Id[A <: WithId](value: A#Id)

class Foo extends WithId {
  type Id = Long
}

then

val fooId: Id[Foo] = Id(12L)

doesn't compile, saying

no type parameters for method apply: (value: A#Id)net.box.Id[A] in object Id exist so that it can be applied to arguments (Long)  --- because --- argument expression's type is not compatible with formal parameter type;  found   : Long  required: ?0A#Id

How can I say and enforce that an Id[Foo] takes a Long?


Solution

  • You're right to get rid of that type parameter on WithId. However, the second problem that arises results from how you're instantiating your Id. When you say val x: Foo[T], you're specifying what type you want x to have, but you're not really helping the compiler out with figuring out what type Foo should use when it's being constructed. So... the compiler error that you're getting results from saying to Foo that you want the Id out of A, but you haven't told Foo what A even is! To fix it, simply change your usage to

    val fooId = Id[Foo](12L)