I´m trying to use a function which use a generic type which is passed as part of the definition of a map.
Here my code
object EventMapping {
val eventMapping = collection.mutable.Map[Class[_ <: EventBase], (User, EventBase) => Unit]()
setMapping(classOf[UserCreated], (user, evt) => user.loadUserName(evt.userName))
private def setMapping[T <: EventBase](clazz: Class[T],fn: (User, T) => Unit) {
eventMapping += clazz -> fn.asInstanceOf[(User, EventBase) => Unit]
}
}
UserCreated class is a en event class that extend from EventBase, so since in setMapping I define T as extended from EventBase, and in setMapping invocation I´m defining which class type I´m using, in the function
user.loadUserName(evt.userName))
I was expecting that evt it would be consider as the CreatedUser event, but still the compiler consider as EventBase.
Similar code base on Java works, but I don't know what I´m missing here.
Here the UserCreated class
class UserCreated @JsonCreator() (@JsonProperty("userName")val userName: String) extends EventBase{
@JsonProperty("userName") def getUserName: String = {
userName
}
}
This is the stack trace
[info] Compiling 5 Scala sources to /Development/proyectX/target/scala-2.11/classes...
[error] /Development/proyectX/app/persistance/EventMapping.scala:11: missing parameter type
[error] setMapping(classOf[UserCreated], (user, evt) => user.loadUserName(evt.asInstanceOf[UserCreated].userName))
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
The issue is that in the definition of setMapping
, you are telling the compiler only that T
is a subtype of EventBase
. So when you call setMapping
and inside the call you have evt.userName
, the compiler can't guarantee that all subtypes of EventBase
support the userName
member, so that's a compile error. So when you do evt.asInstanceOf[UserCreated].userName
, you personally are guaranteeing to the compiler that evt
is really a UserCreated
and it does support the userName
member.
Secondly, the missing parameter type
compile error (not a stack trace btw, stack traces are only from runtime exceptions) is a result of Scala's type inference algorithm not being perfect. Because of a quirk in the algorithm, you can make it more accurate by moving the mapping function ( fn
) into its own parameter list:
object EventMapping {
val eventMapping =
collection.mutable.Map[Class[_ <: EventBase], (User, EventBase) => Unit]()
setMapping(classOf[UserCreated]) { (user, evt) =>
user.loadUserName(evt.userName)
}
private def setMapping[T <: EventBase](
clazz: Class[T])(fn: (User, T) => Unit): Unit =
eventMapping += clazz -> fn.asInstanceOf[(User, EventBase) => Unit]
}
This will also remove the need to downcast evt.asInstanceOf[UserCreated]
because now the compiler can infer it properly.
Lastly, sometimes the type inferencer just can't line everything up properly and still gives you a compile error. In that case you may just pass in a type argument explicitly:
setMapping[UserCreated](classOf[UserCreated]) { (user, evt) =>
user.loadUserName(evt.userName)
}
This tells the compiler that everywhere you have the generic type T
, in this call replace that with UserCreated
.
PS the fact that you have to downcast is usually a sign that you could be using a more idiomatic and composeable Scala feature--typeclasses.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.