问题描述
如何通过只知道它的超类来找到正确的隐式 CommandHandler
类型类?
How can I find the right implicit CommandHandler
type class by knowing only it's super class ?
在 MainRunner 类中,您可以看到以下代码要求我实现 AllCommandHandler
类型类,该类对所有类型进行模式匹配.有没有一种方法可以在不实现 AllCommandsHandler
的情况下为超类型 UserCommand
找到正确的类型类?
In MainRunner class you can see the following code requires me to implement AllCommandHandler
type class which does pattern matching on all the types. Is there a way I can find the right type class for the super type UserCommand
without implementing a AllCommandsHandler
?
val command: UserCommand = CreateUser("userId2")
CommandHandlerRunner.processCommand(command)
我需要这个的原因是因为我的代码的某些部分得到了一个 List[UserCommand]
,它要么来自数据库,要么在验证输入命令后生成.
The reason I need this is because some parts of my code get a List[UserCommand]
which either come from database or get generated after validating an input command.
我的代码
import annotation.implicitNotFound
@implicitNotFound("No member of type class CommandHandler in scope for ${C}")
trait CommandHandler[C <: UserCommand] {
def processCommand(command: C)
}
trait Command
sealed trait UserCommand
case class CreateUser(id: String) extends UserCommand
case class UpdatePassword(id: String, password: String) extends UserCommand
object CommandHandler {
implicit object CreateUserCommandHandler extends CommandHandler[CreateUser] {
override def processCommand(command: CreateUser) = println(command)
}
implicit object UpdateUserPasswordCommandHandler extends CommandHandler[UpdatePassword] {
override def processCommand(command: UpdatePassword) = println(command)
}
//I have to do this which is ugly. Is there a way I can invoke the right CommandHandler without doing the following ?
implicit object AllCommandsHandler extends CommandHandler[UserCommand] {
override def processCommand(command: UserCommand) = command match {
case command: CreateUser =>
CommandHandlerRunner.processCommand(command)
case command: UpdatePassword =>
CommandHandlerRunner.processCommand(command)
case _ =>
sys.error("CommandHandler not found.")
}
}
}
object CommandHandlerRunner {
def processCommand[C <: UserCommand](command: C)(implicit commandHandler: CommandHandler[C]) =
commandHandler.processCommand(command)
}
object MainRunner extends App {
CommandHandlerRunner.processCommand(CreateUser("userId1"))
CommandHandlerRunner.processCommand(UpdatePassword("userId1", "newPassword"))
//In my application in certain situation I get a List[UserCommand] (database query) and I want to invoke processCommand their relevant
//handlers. How do I get this to work ?
val command: UserCommand = CreateUser("userId2")
CommandHandlerRunner.processCommand(command)
}
推荐答案
UserCommand
下面的 CommandHandler
派生是有效的,因为你可以表示一个 UserCommand
作为 CreateUser
或 UpdatePassword
(这就是 UserCommand
是 sealed
很重要的方式).
The CommandHandler
derivation for UserCommand
below works because you can represent a UserCommand
as either a CreateUser
or a UpdatePassword
(that is way it is important that UserCommand
is sealed
).
- 在无形中,来自
UserCommand
的通用表示是CreateUser :+: UpdatePassword :+: CNil
. cnilCommandHandler
和coproductConsCommandHandler
可以为这个通用表示获得一个CommandHandler
实例.genericCommandHandler
使用此通用表示中的实例为您提供UserCommand
的实例(使用Generic[UserCommand]
).
- In shapeless a generic representation from
UserCommand
is thenCreateUser :+: UpdatePassword :+: CNil
. - The
cnilCommandHandler
andcoproductConsCommandHandler
make it possible to get aCommandHandler
instance for this generic representation. genericCommandHandler
uses the instance from this generic representation to give you an instance forUserCommand
(usingGeneric[UserCommand]
).
从 CommandHandler
()中删除上界类型:
Dropping the upper type bound from CommandHandler
(the <: UserCommand
) :
import shapeless._
trait CommandHandler[C] {
def processCommand(command: C): Unit
}
object CommandHandler {
// make it possible to derive a CommandHandler instance for sealed traits
// like UserCommand using shapeless Coproduct and Generic
implicit val cnilCommandHandler: CommandHandler[CNil] =
new CommandHandler[CNil] {
override def processCommand(t: CNil): Unit = ()
}
implicit def coproductConsCommandHandler[L, R <: Coproduct](implicit
lch: CommandHandler[L],
rch: CommandHandler[R]
): CommandHandler[L :+: R] =
new CommandHandler[L :+: R] {
override def processCommand(t: L :+: R): Unit = t match {
case Inl(l) => lch.processCommand(l)
case Inr(r) => rch.processCommand(r)
}
}
implicit def genericCommandHandler[A, G](implicit
gen: Generic.Aux[A, G],
cch: Lazy[CommandHandler[G]]
): CommandHandler[A] =
new CommandHandler[A] {
def processCommand(a: A): Unit = cch.value.processCommand(gen.to(a))
}
}
现在我们可以使用您的 UserCommand
:
Now we can use your UserCommand
:
sealed trait UserCommand
final case class CreateUser(id: String) extends UserCommand
final case class UpdatePassword(id: String, password: String) extends UserCommand
object UserCommand {
implicit val createUserCommandHandler: CommandHandler[CreateUser] =
new CommandHandler[CreateUser] {
override def processCommand(command: CreateUser) = println(command)
}
implicit val updateUserPasswordCommandHandler: CommandHandler[UpdatePassword] =
new CommandHandler[UpdatePassword] {
override def processCommand(command: UpdatePassword) = println(command)
}
}
... 使用您的 CommandHandlerRunner
,即使类型是 UserCommand
:
... with your CommandHandlerRunner
, even if the type is UserCommand
:
object CommandHandlerRunner {
def processCommand[C](command: C)(implicit commandHandler: CommandHandler[C]) =
commandHandler.processCommand(command)
}
val cmd1: UserCommand = CreateUser("foo")
val cmd2: UserCommand = UpdatePassword("id", "open sesame")
CommandHandlerRunner.processCommand(cmd)
// CreateUser(foo)
List(cmd1, cmd2).foreach(CommandHandlerRunner.processCommand[UserCommand] _)
// CreateUser(foo)
// UpdatePassword(id,open sesame)
这可能比你的代码多 AllCommandHandler
,但如果你现在创建了一个 DBCommand
如下:
This may have been more code than you AllCommandHandler
, but if you now created a DBCommand
as follows :
sealed trait DBCommand
final case class Get(id: Int) extends DBCommand
final case class Save[A](a: A) extends DBCommand
并为Get
和Save
创建了CommandHandler
实例,您还将获得一个CommandHandler[DBCommand]
.
And created CommandHandler
instances for Get
and Save
, you would also get a CommandHandler[DBCommand]
.
这篇关于在 Scala 中如何隐式调用只知道它是超类型的类型类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!