Reputation: 73
In Scala 2.11.7, having the following case class and an additional apply
method:
case class FieldValidator[T](key: String, isValid: T => Boolean,
errorMessage: Option[String] = None)
object FieldValidator {
def apply[T](key: String, isValid: T => Boolean,
errorMessage: String): FieldValidator[T] = ???
}
when I try to use:
FieldValidator[String](key, v => !required || v.nonEmpty, "xxx")
I'm getting a "missing parameter type" compilation error pointing at v
.
When I explicitly specify the type of v
, it compiles fine, and I can even skip the generic type of the apply
method, i.e.
FieldValidator(key, (v: String) => !required || v.nonEmpty, "xxx")
Why isn't the type of v
inferred when just the generic type of apply
is provided?
Upvotes: 7
Views: 388
Reputation: 44918
It's not so much about generics, it's rather a problem with overloading and default parameters.
First, recall that since FieldValidator
is a case
-class, a synthetic factory method
def apply(
key: String,
isValid: T => Boolean,
errorMessage: Option[String] = None
)
is automatically added to the companion object FieldValidator
. This results in Field
validator having two generic methods with default parameters and same name.
Here is a shorter example that behaves in roughly the same way:
def foo[A](f: A => Boolean, x: Int = 0): Unit = {}
def foo[A](f: A => Boolean, x: String): Unit = {}
foo[String](_.isEmpty)
It results in:
error: missing parameter type for expanded function ((x$1: ) => x$1.isEmpty)
foo[String](_.isEmpty) ^
I can't pinpoint what exactly goes wrong, but essentially, you have confused the compiler with too much ambiguity by throwing three different sorts of polymorphism at it:
apply
[A]
errorMessage
(x
in my shorter example) can be omitted.Together, this leaves the compiler with the choice between two equally named methods with unclear types and unclear number of expected type arguments. While flexibility is good, too much flexibility is simply too much, and the compiler gives up trying to figure out what you wanted, and forces you to specify all types of every single argument explicitly, without relying on inference.
Theoretically, it could have figured it out in this particular case, but this would require much more complex inference algorithms and much more backtracking and trial-and-error (which would slow down the compilation in the general case). You don't want the compiler to spend half a day playing typesystem-sudoku, even if it theoretically could figure out a unique solution. Exiting quickly with an error message is a reasonable alternative.
Workaround
As a simple work-around, consider reordering the arguments in a way that allows the compiler to eliminate ambiguity as fast as possible. For example, splitting the arguments into two argument lists, with two String
s coming first, would make it unambiguous:
case class FieldValidator[T](
key: String,
isValid: T => Boolean,
errorMessage: Option[String] = None
)
object FieldValidator {
def apply[T]
(key: String, errorMessage: String)
(isValid: T => Boolean)
: FieldValidator[T] = {
???
}
}
val f = FieldValidator[String]("key", "err"){
s => s.nonEmpty
}
Upvotes: 3