Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map on HList fails with subtypes of generic type in Scala & Shapeless

Say we have the following classes and some values (in Scala):

class A[T](val x: T)
class B[T](x: T, val y: T) extends A[T](x)

val x1 = new A("test")
val x2 = new B(1,2)
val x3 = new B("foo","bar")
val x4 = new A(1)

Further, we define the following polymorphic function value (using shapeless):

object f extends (A ~> Option) {
  def apply[T](s: A[T]) = Some(s.x)
}

Now we can call:

f(x1); f(x2); f(x3); f(x4)

Which all succeed (and should IMHO). However:

val list = x1 :: x2 :: x3 :: x4 :: HNil
list.map(f)

// could not find implicit value for parameter mapper:
// shapeless.Mapper[f.type,shapeless.::[A[String],shapeless.::[
//   B[Int],shapeless.::[B[String],shapeless.::[A[Int],shapeless.HNil]]]]]

Where I was expecting:

Some("test") :: Some(1) :: Some("foo") :: Some(1) :: HNil

Note that this works:

val list2 = x1 :: x4 :: HNil // only instances of A
list2.map(f)

UPDATE

It seems that if we specify each case separately, it's fine:

object f extends Poly1 {
  implicit def caseA[T] = at[A[T]]{s => Some(s.x)}
  implicit def caseB[T] = at[B[T]]{s => Some(s.x)}
}

However, trying to express this a bit smarter, does not work (not even for simple applications):

object f extends Poly1 {
  implicit def caseA[T, S <: A[T]] = at[S]{s => Some(s.x)}
}
like image 769
gzm0 Avatar asked May 22 '13 20:05

gzm0


1 Answers

Your best options are one of @TravisBrown's suggestion to use a view bound,

object f extends Poly1 {
  implicit def caseA[T, S <% A[T]] = at[S]{s => Some(s.x)}
}

or, more or less equivalently, a type constraint,

object f2 extends Poly1 {
  implicit def caseA[S, T](implicit ev : S <:< A[T]) = at[S]{s => Some(s.x)}
}

or a variation on your two case solution which factors out the commonality,

object f3 extends Poly1 {
  def asub[T](s: A[T]) = Some(s.x)
  implicit def caseA[T] = at[A[T]](asub)
  implicit def caseB[T] = at[B[T]](asub)
}

It's unlikely that shapeless's polymorphic function values will be changed to directly support the kind of argument type variance needed to make the initial definition work as you wanted, because that would conflict with the (highly desirable IMO) ability to discriminate type-specific cases very precisely.

like image 181
Miles Sabin Avatar answered Nov 17 '22 14:11

Miles Sabin