【问题标题】:Scala, passing values to a collection with AnyScala,使用 Any 将值传递给集合
【发布时间】:2014-02-13 03:43:22
【问题描述】:

我正在为我的网络应用程序中的操作构建一个框架。基本思想是它离线工作,所以我需要一种方法来传递发生的动作,然后可以倒带、合并、重放等等。 ActionMeta 是启用此行为的类,并表示单个动作,类型 S 是动作的主题类型。这是构成它的实际论据。

case class ActionMeta[S](
  val timestamp: Instant,
  val syncKey: SyncKey,

  val action: Action[S],
  val subjectId: UUID,
  val subjectClientId: UUID,

  val originMeta: JsValue,
  val actionArgs: JsValue,
  val status: ActionStatus,
  val syncStatus: ActionSyncStatus,
  val subActions: List[(Option[Any], ActionMeta[Any])]
) {
}

这和它的行为符合我对单个动作和具有相同主题的动作堆栈的规范,但现在我需要解决最后一行,即子动作。关键问题是子操作往往会涉及不同的主题。动作本身作为对象继承自这些特征:

trait Action[S] {
  val registryKey: String
  ActionRegistry.register(registryKey, this)

  def getSubjectIds(subject: S): (UUID, UUID)
  def pullOriginMeta(subject: S): JsValue

  def getSubjectRepresentation(id: UUID, clientId: UUID): S
  def saveSubjectRepresentation(subject: S): S
  def merge(args: JsValue, newArgs: JsValue): Option[JsValue]
  def deleteSubjectRepresentation(id: UUID, clientId: UUID): Boolean
}

trait CreationAction[S] extends Action[S] {
  def apply(actionArgs: JsValue = JsNull): (S, ActionMeta[S]) = {
    val (res, updatedActionArgs) = this.forwards(actionArgs)
    val (sid, scid) = this.getSubjectIds(res)
    val actionMeta = new ActionMeta[S](
      DateTime.now.toInstant, new SyncKey(), this, sid, scid,
      JsNull, updatedActionArgs, Done, LocalAction, this.runSubActions(actionArgs)
    )
    (res, actionMeta)
  }

  def forwards(args: JsValue): (S, JsValue)
  def backwards(subject: S, args: JsValue): JsValue

  def runSubActions(forwardArgs: JsValue): List[(Option[Any], ActionMeta[Any])] = {
    List()
  }
}

还有 TransformAction[S] 和 DeletionAction[S] trait,它们相似,但 forwards/backwards 的类型签名不同,apply 的逻辑不同。

object TestSideActionCreate extends {val registryKey = "actions:testSideCreate"}
  with TestSideActionBase with CreationAction[TestSide] {

  // omitted method bodies, not necessary to problem
  def forwards(args: JsValue): (TestSide, JsValue) = ???
  def backwards(subject: TestSide, args: JsValue): JsValue = ???
  def merge(args: JsValue, newArgs: JsValue): Option[JsValue] = ???      

  override def runSubActions(args: JsValue): List[(Option[Any], ActionMeta[Any])] = {
    List(
      TestActionCreate(
        Json.obj("id" -> UUID.randomUUID.toString)
      ).asInstanceOf[(Option[Any], ActionMeta[Any])]
    )
  }
}

基本上我的问题是runSubActions 就目前而言是笨重的。这个方法确实有效,我可以访问生成的子操作,就像我期望的那样,类型完整,但是,这仍然是我要做很多最终实现的地方,我的 API,所以我不想如果我可以帮助的话,通过使用 asInstanceOf 来强制编译器通过一系列不同类型的子操作。

在类型系统中是否有更好的方式来表达这一点?我需要能够接受List[Option[Any], ActionMeta[Any]] 中的任何内容,因为我不想束手无策:只要它实现了ActionMeta[S] 并具有相应的Action[S],我就可以依靠它来实现可预测的行为.

干杯。

【问题讨论】:

    标签: scala collections types type-parameter


    【解决方案1】:

    听起来你几乎想要HList。这是一个异构类型的列表,可以处理和保存各种类型的列表。因此,您可以将Int :: Double :: Foo :: HNil 作为您的列表,并且该列表中的每个单独条目仍然知道它的类型。 Scala 中最好、最强大、最完整的例子是 Miles 的 Sabin Shapeless

    也就是说,Shapeless 有它的取舍,也许一种包装你的行动以隐藏或减轻对S 的需求的方法会更好:

     trait ActionOp[S]{
       def ids(that: S): (UUID, UUID)
     }
    
     trait Action{
       def getSubjectIds(that: S)(implicit ops: ActionOps[S]) = ops ids that
     }
    

    您可能将类型传递给类型类的东西?然后你可以重写你的代码来处理类型类的“接口”,而不用担心S的实际类型。

    【讨论】:

    • 包装听起来更符合我想做的事情。想我会在继续之前考虑一下我该怎么做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    • 2018-09-06
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多