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:
- Overloading: you have two methods with name
apply
- Generics: your methods have a generic type parameter
[A]
- Default arguments: your
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 Strings 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
}
case class FieldValidator, and leave onlyobject FieldValidator. There seems to be some kind of collision between the user-definedapplyand the synthetic methods. - Andrey Tyukin