【问题标题】:In Scala how to implicitly invoke a type classes knowing only it's super type?在 Scala 中,如何隐式调用类型类,只知道它是超类型?
【发布时间】:2016-06-15 01:50:02
【问题描述】:

我怎样才能通过只知道它的超类来找到正确的隐式CommandHandler 类型类?

在 MainRunner 类中,您可以看到以下代码要求我实现 AllCommandHandler 类型类,该类对所有类型进行模式匹配。有没有一种方法可以在不实现 AllCommandsHandler 的情况下为超类型 UserCommand 找到正确的类型类?

val command: UserCommand = CreateUser("userId2")
CommandHandlerRunner.processCommand(command)

我需要这个的原因是因为我的代码的某些部分得到了一个List[UserCommand],它要么来自数据库,要么在验证输入命令后生成。

我的代码

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 是一个密封的特征,这应该可以使用 shapeless。
  • 是的,它是sealed trait。我已经更新了代码。我将尝试阅读 shapeless。

标签: scala


【解决方案1】:

下面的UserCommandCommandHandler 派生是有效的,因为您可以将UserCommand 表示为CreateUserUpdatePassword(也就是说UserCommandsealed 很重要)。

  • 在无形中,来自UserCommand 的通用表示是CreateUser :+: UpdatePassword :+: CNil
  • cnilCommandHandlercoproductConsCommandHandler 使获取此通用表示的 CommandHandler 实例成为可能。
  • genericCommandHandler 使用此通用表示中的实例为您提供UserCommand 的实例(使用Generic[UserCommand])。

CommandHandler&lt;: 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

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

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,如下所示:

sealed trait DBCommand
final case class Get(id: Int) extends DBCommand
final case class Save[A](a: A) extends DBCommand

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

【讨论】:

  • 哇!这只是哇! ...巫术哈哈!太感谢了。我将不得不理解这背后的理论,但是哇!希望我能更多地支持这个。
  • Wizardry 确实是一个用来描述你可以用 Shapeless 做的事情的词,但确实需要时间让这些事情变得有意义......即使这样,它仍然感觉像魔法;-)
  • 在我的问题中,我很遗憾没有将state:S 参数放在函数processCommand 中,实际函数是def processCommand(command: C, state: S): S。我正在尝试填写您的代码中的空白,但我不知道如何在 coproductConsCommandHandlergenericCommandHandler 中添加 S: State
  • @hajime 您应该能够将state 从一个processCommand 传递到下一个。对于cnilCommandHandler,它取决于S 的类型,可能只是processCommand(t: CNil, state: S): S = state
  • @hajime 暂时没时间,我明天去看看
猜你喜欢
  • 2018-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多