Shapeless: Checking Type Constraints of Polymorphic functions
Asked Answered
S

1

7

I'm working on a small library for economic models that check the Units of the entities, using Types, e.g. instead of val apples = 2.0 we write val apples = GoodsAmount[KG, Apples](2.0). For creating bundle of goods, I trying to use HLists from the shapeless library. This works fine, but in some cases I can not be as generic code as I prefer. See e.g. the following problem.

I start with a simple code that explain what I want to lift into shapeless. We create two classes, on that represent Km, the other Miles. It should be allowed to add Km classes, but not miles. That I use a abstract type T is mainly motivated be our more complex library. And the indirect call to the '+' function is just because we need something similar in the shapeless case behind.

trait Foo {
  type T
  val v: Double
  def +[B <: Foo](other: B)(implicit ev: this.T =:= other.T) = v + other.v
}

trait _Km 
trait _Miles

case class Km(v: Double)    extends Foo { type T = _Km }
case class Miles(v: Double) extends Foo { type T = _Miles }

object ExampleSimple extends App {
  def add[A <: Foo, B <: Foo](a: A, b: B)(implicit ev: a.T =:= b.T) = { a + b }

  add(Km(1), Km(2))
  // add(Km(1), Miles(2)) /* does not compile as intended */
}

This works as intended. But it's necessary to have the Type Contraint check on the 'add' function. My attempt to extend this to HLists looks like this:

object ExampleShapeless extends App {
  import shapeless._

  val l1 = Km(1) :: Km(2) :: HNil
  val l2 = Km(4) :: Km(3) :: HNil

  object add extends Poly1 {
    implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a, b) => a + b }
  }

  (l1 zip l2).map(add)
}

But this generate the following error message (using Scala 2.10.2):

[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:50: Cannot prove that a.T =:= b.T.
[error]     implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a: Foo, b) => a + b }
[error]                                                                          ^
[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:54: could not find implicit value for parameter mapper: shapeless.Mapper[ExampleShapeless.add.type,shapeless.::[(Km, Km),shapeless.::[(Km, Km),shapeless.HNil]]]
[error]   (l1 zip l2).map(add)

The first error should be fixed, in the case that I could add a Type Constraint to the caseTuple function, but to be honest, I have not understood how the at function is working and where I could add the implicit evidence parameter. And I'm also don't know, what I must do, so that the Mapper would find his implicit value.

A less generic version, where I replase the caseTuple function with

implicit def caseTuple = at[(Km,Km)] { case (a, b) => a + b }

works fine, but would need to write a lot of redundant code (okay, this solution would be still better as our current solution using Tuples). Can somebody give me a hint how I can solve this problem?

Thanks, Klinke

Susannahsusanne answered 27/6, 2013 at 14:38 Comment(1)
You could try to define your Foo like this: trait Foo[T <: Foo] { v: Double; +(t T): T =...}. class Km(val v: Double) extends Foo[Km]. implicit def add[T] = at[(Foo[T], Foo[T])]Daff
K
7

You can require the type members to match by adding a type parameter to the case:

object add extends Poly1 {
  implicit def caseTuple[_T, A <: Foo { type T = _T }] = at[(A, A)] {
    case (a, b) => a + b
  }
}

Or you could use an existential type, since you only really care that they're the same:

object add extends Poly1 {
  implicit def caseTuple[A <: Foo { type T = _T } forSome { type _T }] =
    at[(A, A)] {
      case (a, b) => a + b
    }
}

Either version will provide the behavior you want.

Kumquat answered 27/6, 2013 at 15:17 Comment(4)
Thanks, this works fine also my more complex case ;-) But I have still the problem with the missing implicit value for the Mapper. I will try to solve it myself, but maybe you can help me here too?Susannahsusanne
Okay, found a solution in for the simplified version, adding a context bound for A comes to help. So I have now implicit def caseTuple[_t, A <: Foo { type T = _T } <% Foo { type T = _T }] = ... This time the solution doesn't translate so easily to my full version, but hopefully I can fix the new problems too.Susannahsusanne
@Klinke: I'm not sure I understand the problem—if I copy and paste my add into your ExampleShapeless everything seems to work as expected.Kumquat
I just tested with the actual shapeless source, but still get this compiler error with your add object: could not find implicit value for parameter mapper: shapeless.Mapper[ExampleShapeless.add.type,shapeless.::[(Km, Km),shapeless.::[(Km, Km),shapeless.HNil]]]. Adding (implicit ev: A => Foo { type T = _T }) to the caseTuple functions helps, also in my library.Susannahsusanne

© 2022 - 2024 — McMap. All rights reserved.