MalucoMarinero
MalucoMarinero

Reputation: 359

Scala, passing values to a collection with Any

I'm building a framework for Actions in my web app. The basic idea is that it works offline, so I need a way to pass around actions that occur, which can then be rewound, merged, replayed and so on. ActionMeta is the class that enables this behaviour, and represents a single action, type S being the subject Type of an action. Here's the actual arguments that make it up.

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])]
) {
}

This and its behaviours work to my specs for single actions and stacks of actions with the same subject, but now I need to address the final line, namely sub actions. The key issue is sub actions will more often than not involve a different subject. Actions themselves are inherited as objects from these traits:

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()
  }
}

There is also the TransformAction[S] and DeletionAction[S] trait, which are similar but have different type signatures for forwards/backwards, and different logic for the 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])]
    )
  }
}

Basically my problem is that runSubActions as it stands is clunky. This method does work, I'm able to access the resulting sub actions as I would expect with type intact, but still, this is where I'd be doing lots of my end implementation, my API, so I don't want to be forcing the compiler to let through a range of sub actions of different types by resorting to asInstanceOf if I can help it.

Is there a better way to express this in the Type system? I need to be able to accept anything in the List[Option[Any], ActionMeta[Any]], as I don't wish to have my hands tied: as long as it implements ActionMeta[S] and has a corresponding Action[S], I can rely on it to behave predictably.

Cheers.

Upvotes: 0

Views: 100

Answers (1)

wheaties
wheaties

Reputation: 35970

Sounds almost like you want an HList. This is a heterogeneously typed list which can handle and preserve the various types of a list. Hence, you could have Int :: Double :: Foo :: HNil as your list and each individual entry in that list would still know it's type. The best, most power, and most complete example of this in Scala is Miles' Sabin Shapeless.

That said, Shapeless has it's trade offs and maybe a way to wrap your actions to hide or alleviate the need for S would be better in order:

 trait ActionOp[S]{
   def ids(that: S): (UUID, UUID)
 }

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

Something where you're passing the type to a type class perhaps? Then you could rewrite your code to work on the "interface" of the type class and not worry about the actual type of S.

Upvotes: 1

Related Questions