为什么 Scala 不能在显而易见的情况下推断类型参数?

Why Scala cannot infer type argument when it's obvious?

在下面的示例中,我尝试在 MySourceTypedPipe[T] 之间创建隐式转换。我拥有 MySource,事实上我有很多这样的来源,所以我想使用 Porable[T] 特征来标记我想要输出 TypedPipe[T] 的参数类型 T ,以便隐式转换可以自动执行 .toTypedPipe[T] 部分(这样我就不必在使用它们时为我拥有的每个来源编写 .toTypedPipe[T])。

import scala.language.implicitConversions

// The following two are pre-defined and I cannot change them

class TypedPipe[T](val path: String) {
  def mapWithValue = {
    println("values from " + path + " of type " + this.getClass)
  }
}

class Source(val path: String) {
  def toTypedPipe[T] = { new TypedPipe[T](path) }
}

// The following are defined by me, so yes I can change them.    

trait Portable[T]

class MySource(p: String) extends Source(p) with Portable[Tuple2[Int, Long]]

object conversions {
  implicit def portableSourceToTypedPipe[S <: Source with Portable[T], T](source: S): TypedPipe[T] = {
    source
      .toTypedPipe[T]
  }
}

import conversions._

portableSourceToTypedPipe(new MySource("real_path")).mapWithValue

但问题是,Scala 似乎无法推断出 T 最后一个语句:

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> class TypedPipe[T](val path: String) {
     |   def mapWithValue = {
     |     println("values from " + path + " of type " + this.getClass)
     |   }
     | }
defined class TypedPipe

scala> class Source(val path: String) {
     |   def toTypedPipe[T] = { new TypedPipe[T](path) }
     | }
defined class Source

scala>

scala> trait Portable[T]
defined trait Portable

scala>

scala> class MySource(p: String) extends Source(p) with Portable[Tuple2[Int, Long]]
defined class MySource

scala> object conversions {
     |   implicit def portableSourceToTypedPipe[S <: Source with Portable[T], T](source: S): TypedPipe[T] = {
     |     source
     |       .toTypedPipe[T]
     |   }
     | }
defined module conversions

scala> import conversions._
import conversions._

scala> portableSourceToTypedPipe(new MySource("real_path")).mapWithValue
<console>:17: error: inferred type arguments [MySource,Nothing] do not conform to method portableSourceToTypedPipe's type parameter bounds [S <: Source with Portable[T],T]
              portableSourceToTypedPipe(new MySource("real_path")).mapWithValue
              ^
<console>:17: error: type mismatch;
 found   : MySource
 required: S
              portableSourceToTypedPipe(new MySource("real_path")).mapWithValue
                                        ^

scala>

从示例中可以看出,MySource 实现了 Portable[Tuple2[Int, Long]],因此 T 应该是 Tuple2[Int, Long]。为什么不以这种方式推断(这会使示例有效)?

编辑:

this question and its answer mentioned by n.m. 之后,我修改了我的代码以使用隐式证据参数来表达两个类型参数之间的关系。显式转换调用现在有效,但隐式转换调用无效。所以还是需要帮助。

scala> object conversions {
     |   implicit def portableSourceToTypedPipe[S, T](source: S)(implicit ev: S <:< Source with Portable[T]): TypedPipe[T] = {
     |     source
     |       .toTypedPipe[T]
     |   }
     | }
defined module conversions

scala> import conversions._
import conversions._

scala> portableSourceToTypedPipe(new MySource("real_path")).mapWithValue
values from real_path of type class $line4.$read$$iw$$iw$TypedPipe

scala> (new MySource("real_path")).mapWithValue
<console>:17: error: Cannot prove that MySource <:< Source with Portable[T].
              (new MySource("real_path")).mapWithValue
               ^
<console>:17: error: value mapWithValue is not a member of MySource
              (new MySource("real_path")).mapWithValue

EDIT2

我选择特征 Portable[T] 的原因是它可以与多个基础 Source 类型一起使用。熟悉 Scalding 的人可能知道我们有多种来源,例如DailySuffixSource、HourlySuffixSource,更不用说可以插入其他特征,如 SuccessFileSourceDelimitedScheme。必须为每个基础 source/traits 组合实现一些东西将需要相当多的工作。因此我的特质选择。当然这不是必须的 - 任何可以使用多个基础 source/traits 组合和 O(1) 实现量的答案都可以。

您的 Source 定义说您可以为 任何 T 调用 toTypedPipe[T]。如果你真的想让 MySource 只转换成 Tuple2[Int, Long],它应该是

class TypedSource[T](path: String) extends Source(path) {
  def toSpecificTypedPipe = toTypedPipe[T]
}

class MySource(p: String) extends TypedSource[(Int, Long)](p)

implicit def portableSourceToTypedPipe[T](source: TypedSource[T]): TypedPipe[T] = 
  source.toSpecificTypedPipe

(对于 TypedSource,您也可以使用组合而不是继承。)

如果您确实希望将能力转换为任何类型 "preferred",只需删除 S,您不需要它:

implicit def portableSourceToTypedPipe[T](source: Source with Portable[T]): TypedPipe[T]

鉴于您没有在 return 类型中的任何地方使用类型参数 S,为什么不让 portableSourceToTypedPipe 使用 Source with Portable[T]。 换句话说:

implicit def portableSourceToTypedPipe[T](source: Source with Portable[T]): TypedPipe[T]

这很容易解决您的编译问题。 通常,您越明确,编译器解决由类型参数表示的约束的可能性就越大。首先是完全删除不必要的类型参数。