2

In the following example, I was trying to create an implicit conversion between MySource and TypedPipe[T]. I own MySource, in fact I have a lot of such sources, so I wanted to use a Porable[T] trait to mark what type argument T I want for the output TypedPipe[T], so that the implicit conversion can automatically do the .toTypedPipe[T] part (so that I don't have to write .toTypedPipe[T] for every source I have when I use them).

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

But the problem is, Scala seem to not be able to infer T for the last statement:

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>

From the example, it's pretty obvious that MySource implements Portable[Tuple2[Int, Long]], so T should be Tuple2[Int, Long]. Why it's not inferred this way (which would have made the example work)?

EDIT:

Following this question and its answer mentioned by n.m., I revised my code to use an implicit evidence parameter to express the relation between the two type arguments. The explicit conversion call works now, but not the implicit conversion call. So still need help.

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

The reason for me to choose a trait Portable[T] is that it can potentially work with multiple base Source types. One familiar with Scalding might know that we have many types of sources, e.g. DailySuffixSource, HourlySuffixSource, not to mention that one can plug-in other traits like SuccessFileSource and DelimitedScheme. Having to implement something for each base source/traits combination will need quite a bit of work. Thus my trait choice. Of course it's not a must - any answer that can with multiple base source/traits combinations with O(1) amount of implementation will do.

Community
  • 1
  • 1
Roy
  • 880
  • 1
  • 12
  • 27
  • possible duplicate of [Scala inferred type arguments - Type bounds inferring to 'Nothing'](http://stackoverflow.com/questions/16291313/scala-inferred-type-arguments-type-bounds-inferring-to-nothing) – n. m. could be an AI Jun 09 '15 at 08:01
  • Thanks for pointing out. After used the implicit evidence, the explicit conversion call (i.e. the last statement) worked. But the implicit conversion still doesn't work. Will edit the question to add these. – Roy Jun 09 '15 at 08:10
  • Given that you are not using the type parameter `S` anywhere in the return type, why not just have `portableSourceToTypedPipe` take a `Source with Portable[T]` (as in `implicit def portableSourceToTypedPipe[T](source: Source with Portable[T]): TypedPipe[T]`) ? This trivially fixes your compilation problem. – Régis Jean-Gilles Jun 09 '15 at 08:38
  • @Régis: Ah! It worked as a charm! So I guess the takeaway here is that if a type parameter is not explicitly used anywhere in the code, we should try to remove it from the parameter list. – Roy Jun 09 '15 at 08:46
  • BTW, do you know why this doesn't work? `implicit def portableSourceToTypedPipe[S, T](source: S)(implicit ev: S <:< Source with Portable[T]): TypedPipe[T] = { ... }` – Roy Jun 09 '15 at 08:47
  • Yes, that's a good rule of thumb, because very type parameter is a basically a new variable that you are inputting in a relatively limited constraint solver. I'll add it as an answer then. – Régis Jean-Gilles Jun 09 '15 at 08:48
  • Sounds good, man. Thanks a lot. It will be even better if you could enlighten us as why the above evidence doesn't work for the implicit conversion. – Roy Jun 09 '15 at 08:50
  • Well, I've just tried the alternative with an evidence, and it does work (it works because with the evidence, scala first infers `S`, and then only infers `T` as a second step, as opposed to trying to do both at once). Can you check again? Then again, you don't **need** the evidence, because you don't use `S` anywhere, so better just remove it altogether. – Régis Jean-Gilles Jun 09 '15 at 08:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/80027/discussion-between-roy-and-regis-jean-gilles). – Roy Jun 09 '15 at 09:01
  • `implicit def portableSourceToTypedPipe[S <: Source,T](source: S)(implicit b : S <:< Portable[T]) = {...}` should work. – n. m. could be an AI Jun 09 '15 at 13:14
  • The evidence-based solution posted by Roy himself actually works too, starting from scala 2.11.0-M7 (requires bug-fix https://issues.scala-lang.org/browse/SI-3346) – Régis Jean-Gilles Jun 09 '15 at 14:00

2 Answers2

3

Given that you are not using the type parameter S anywhere in the return type, why not just have portableSourceToTypedPipe take a Source with Portable[T]. In other words:

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

This trivially fixes your compilation problem. In general, the more explicit you are, the higher the chances are that the compiler can resolve the constraints represented by the type parameters. This starts with removing unnecessary type parameters altogether.

Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
1

Your Source definition says you can call toTypedPipe[T] for any T. If you actually want MySource to convert to Tuple2[Int, Long] only, it should be

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

(you could also use composition instead of inheritance for TypedSource.)

If you do want ability convert to any type with one "preferred", just get rid of S, you don't need it:

implicit def portableSourceToTypedPipe[T](source: Source with Portable[T]): TypedPipe[T]
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • I don't own `Source`. It's predefined and I cannot change it. That's why created `MySource` and tried to make `MySource` type specific. Let me update my question so others know what I own and what I don't. – Roy Jun 09 '15 at 08:23
  • Thank you Alexey. Your answer is elegant if we are only working with one `Source` type (which was my mistake that I didn't thought about mentioning there might be multiple base source types). I will revise the question and hope to see an answer that works with multiple base source types easily (i.e. without having to implement something for each of the base source). – Roy Jun 09 '15 at 08:36