Map on HList fails with subtypes of generic type i

2020-07-06 06:13发布

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)}
}

1条回答
家丑人穷心不美
2楼-- · 2020-07-06 06:41

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.

查看更多
登录 后发表回答