Почему Scala не может вывести аргумент типа, когда он очевиден?

В следующем примере я пытался создать неявное преобразование между MySource а также TypedPipe[T], я владею MySourceНа самом деле у меня много таких источников, поэтому я хотел использовать Porable[T] черта, чтобы отметить, какой тип аргумента 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

Но проблема в том, что Скала, похоже, не сможет сделать вывод 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., я изменил свой код, чтобы использовать неявный параметр доказательства, чтобы выразить связь между двумя типами аргументов. Явный вызов преобразования работает сейчас, но не неявный вызов преобразования. Так что все еще нужна помощь.

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, не говоря уже о том, что можно подключить другие черты типа SuccessFileSource а также DelimitedScheme, Необходимость реализовать что-то для каждой комбинации базовый источник / черты потребует довольно много работы. Таким образом, моя черта выбора. Конечно, это не обязательно - подойдет любой ответ, который может с несколькими базовыми комбинациями источник / черты с O(1) количеством реализации.

2 ответа

Решение

Учитывая, что вы не используете параметр типа S нигде в возвращаемом типе, почему бы просто не иметь portableSourceToTypedPipe взять Source with Portable[T], Другими словами:

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

Это тривиально устраняет проблему компиляции. В общем, чем более явным вы являетесь, тем выше вероятность того, что компилятор сможет разрешить ограничения, представленные параметрами типа. Это начинается с полного удаления ненужных параметров типа.

Ваш Source определение говорит, что вы можете позвонить toTypedPipe[T] для любого 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]
Другие вопросы по тегам