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?
Generic Methods A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
Notice that the type variables declared by a generic type can be used only by the instance fields and methods (and nested types) of the type and not by static fields and methods. The reason, of course, is that it is instances of generic types that are parameterized.
A generic function has a set of bodies of code of which a subset is selected for execution. The selected bodies of code and the manner of their combination are determined by the classes or identities of one or more of the arguments to the generic function and by its method combination type.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With