本文介绍了在 Scala 中如何隐式调用只知道它是超类型的类型类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何通过只知道它的超类来找到正确的隐式 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 作为 CreateUserUpdatePassword(这就是 UserCommandsealed 很重要的方式).

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.
  • cnilCommandHandlercoproductConsCommandHandler 可以为这个通用表示获得一个 CommandHandler 实例.
  • genericCommandHandler 使用此通用表示中的实例为您提供 UserCommand 的实例(使用 Generic[UserCommand]).
  • In shapeless a generic representation from UserCommand is then CreateUser :+: UpdatePassword :+: CNil.
  • The cnilCommandHandler and coproductConsCommandHandler make it possible to get a CommandHandler instance for this generic representation.
  • genericCommandHandler uses the instance from this generic representation to give you an instance for UserCommand (using Generic[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

并为GetSave 创建了CommandHandler 实例,您还将获得一个CommandHandler[DBCommand].

And created CommandHandler instances for Get and Save, you would also get a CommandHandler[DBCommand].

这篇关于在 Scala 中如何隐式调用只知道它是超类型的类型类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-28 11:14