简体   繁体   中英

Function with Generic type that extend class

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM