【问题标题】:Why Scala cannot infer type argument when it's obvious?为什么 Scala 不能在很明显的情况下推断类型参数?
【发布时间】:2015-06-09 16:54:28
【问题描述】:

在以下示例中,我试图在 MySourceTypedPipe[T] 之间创建隐式转换。我拥有MySource,其实我有很多这样的来源,所以我想使用Porable[T] trait 来标记我想要输出TypedPipe[T] 的类型参数TypedPipe[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]。为什么不以这种方式推断(这会使示例工作)?

编辑:

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

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。必须为每个基本源/特征组合实现一些东西需要相当多的工作。因此我的特质选择。当然这不是必须的 - 任何可以使用多个基本源/特征组合和 O(1) 实现量的答案都可以。

【问题讨论】:

  • 感谢您的指出。使用隐式证据后,显式转换调用(即最后一条语句)起作用。但是隐式转换仍然不起作用。将编辑问题以添加这些。
  • 鉴于您没有在返回类型中的任何地方使用类型参数S,为什么不让portableSourceToTypedPipe 使用Source with Portable[T](如implicit def portableSourceToTypedPipe[T](source: Source with Portable[T]): TypedPipe[T])?这可以轻松解决您的编译问题。
  • @Régis:啊!它起到了魅力的作用!所以我想这里的要点是,如果一个类型参数没有在代码的任何地方显式使用,我们应该尝试将它从参数列表中删除。
  • 顺便说一句,你知道为什么这不起作用吗? implicit def portableSourceToTypedPipe[S, T](source: S)(implicit ev: S &lt;:&lt; Source with Portable[T]): TypedPipe[T] = { ... }

标签: scala scalding


【解决方案1】:

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

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

这可以轻松解决您的编译问题。 通常,您越明确,编译器可以解决类型参数表示的约束的机会就越高。首先要完全删除不必要的类型参数。

【讨论】:

    【解决方案2】:

    您的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,您也可以使用组合而不是继承。)

    如果您确实希望将能力转换为具有“首选”的任何类型,只需删除 S,您就不需要它:

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

    【讨论】:

    • 我不拥有Source。它是预定义的,我无法更改。这就是为什么创建 MySource 并尝试使 MySource 类型特定的原因。让我更新我的问题,以便其他人知道我拥有什么以及我没有什么。
    • 谢谢阿列克谢。如果我们只使用一种 Source 类型(这是我的错误,我没有考虑提到可能有多种基本源类型),那么您的答案很优雅。我将修改这个问题,并希望看到一个适用于多种基本源类型的答案(即无需为每个基本源实现某些东西)。
    猜你喜欢
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多