问题
I'm attempting to write a simple query monad and am having trouble getting my generic type annotations correct.
My first attempt went as follows (vastly simplified for conciseness)
case class Person( val name: String )
abstract class Schema[T]
object People extends Schema[Person]
case class Query[U <: Schema[T], T]( schema: U ) { <---- Type signature
def results: Seq[T] = ...
def where( f: U => Operation ) = ...
}
class TypeText extends Application {
val query = Query( People ) <---- Type inference fails
}
The compiler didn't like this, as it couldn't infer the type of 'T'.
error: inferred type arguments [People.type,Nothing] do not conform to method apply's type parameter bounds [U <: Schema[T],T]
While experimenting I found that using view bounds instead works as expected
case class Query[U <% Schema[T], T]( schema: U ) {
(Note the use of view bound "<%" instead of type bound "<:")
However in my limited understanding of the type system, since I'm expecting an actual subclass (and not just convertibility) of Schema[T], I would assume type bound "<:" is the correct bounds to be using here?
If this is the case, what am I missing - how do I give the compiler enough hints to infer T correctly when using type bounds instead of view bounds?
回答1:
In order to encode the relationship between the two type parameters, you can use something like
case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... }
See §4.3 and §4.4 of the Scala Language Spec for more info.
回答2:
This is not a fully statisfying answer (at least to me) as I have to admit that I cannot put words on exactly where and why the inference fails here. I only have some fuzzy intuitions about it. The problem is related to the compiler having to infer two type parameters at a time. As to why changing the type bound to a view bound fixes the compilation, my understanding is that now there are two parameter lists, and that as a result we now have two successive phases of type inferences instead of two inferences at a time. Indeed, the following:
case class Query[U <% Schema[T], T]( schema: U )
is the same as:
case class Query[U, T]( schema: U )( implicit conv: U => Schema[T] )
The first parameter list drives the inference of U
, and then the second one (note that U
is now know) will drive the inference of T
.
In the case of the expression Query( People )
, the parameter People
will drive the type inferencer to set U
to People.type
. Then, the compiler will look for an implicit conversion from People.type
to Schema[T]
, to pass in the second parameter list. The only one in scope is the (trivial) conversion from People.type
to Schema[Person]
, driving the inferencer to deduce that T = Person
.
To fix the compilation without resorting to a view bound, you can replace the type parameter T
with an abstract type:
case class Person( val name: String )
sealed trait Schema {
type T
}
abstract class SchemaImpl[_T] extends Schema {
type T = _T
}
object People extends SchemaImpl[Person]
case class Query[U <: Schema]( schema: U ) {
def results: Seq[schema.T] = ???
}
class TypeText extends Application {
val query = Query( People )
}
UPDATE:
@Aaron Novstrup's:
To the extent of my knowledge, your answer is incorrect (update to the update: the orignal answer from Aaron claimed that the Query
declaration was equivalenbt to case class Query[U <: Schema[X], T](schema: U)
).
case class Query[U <: Schema[X], T](schema: U)
does not even compile. Let's say that you meant
case class Query[U <: Schema[_], T](schema: U)
(which does compile), it's easy to check in the REPL that it is not the same either.
Indeed, the following compiles fine:
case class Query[U <: Schema[_], T](schema: U)
type MyQuery = Query[Schema[String], Int]
While, the following does not:
case class Query[U <: Schema[T], T](schema: U)
type MyQuery = Query[Schema[String], Int]
Hence proving the difference. The error is:
<console>:10: error: type arguments [Schema[String],Int] do not conform to class Query's type parameter bounds [U <: Schema[T],T]
type MyQuery = Query[Schema[String], Int]
Which clearly shows that the first and second occurences of T
denote the same type, and we do have a relationship between the two type parameters.
回答3:
I had the same problem. The following worked for me:
case class Query[U <: Schema[T], T]( schema: U with Schema[T] ) {
...
}
回答4:
I've always found that when using two type identifiers on a class/function, the type inference system does not work as expected and you have to be explicit like so:
val query = Query[People.type, Person]( People )
If you changed your Query
declaration to this:
case class Query[U <: Schema[_]( schema: U )
You'd be able to do this:
val query = Query( People )
But then you would not know the underlying type of the Schema
supplied and would not be able to properly implement the results
function.
来源:https://stackoverflow.com/questions/16291313/scala-inferred-type-arguments-type-bounds-inferring-to-nothing