1

I'll explain this directly in my domain, because it would get confusing otherwise...

In MtG, there are replaceable events, e.g. gaining life. I want to use partial functions to do the replacement before an event executes. To do so, every event needs to be an instance of a case class.

//a replaceable event
trait Event { def execute(): Unit }
//an effect that replaces an event
type ReplacementEffect = PartialFunction[Event, Event]

//applies all effects to the event. If a replacement doesn't match, the
//event remains unchanged
def replace(event: Event, effects: ReplacementEffect*) =
  effects.foldLeft(event) { case (event, effect) => effect.applyOrElse(event, identity[Event] _) }

def executeReplaced(event: Event) = replace(event, activeEffects: _*).execute()

//example: A player's life points

var _life = 20

//one instance of a replaceable event
case class GainLife(life: Int) extends Event {
  def execute() = {
    // ====== here goes the Interesting Game Logic
    _life += life
  }
}

def gainLife(life: Int) =
  // ====== the place where Interesting Game Logic belongs logically
  executeReplaced(GainLife(life))

//execution:

val double: ReplacementEffect = {
  //gain twice as much life
  case GainLife(x) => GainLife(x * 2)
}
//one effect is active
val activeEffects = List(double)

//gain six life
println(_life)
gainLife(3)
println(_life)

if I didn't have the requirement for replacements, I could condense (parts) to the following:

//a replaceable event
type Event = () => Unit

//example: A player's life points

//one instance of a replaceable event
def gainLife(life: Int) = executeReplaced { () =>
  _life += life
}

What I like better about this code is that the Interesting Game Logic is nested in the gainLife method where it belongs. Is it possible to keep this more locally, even when I need case classes? The best I could come up with is this, but besides looking clumsy by itself, the GainLife case class is private to the block, so it's just as usable as no case class at all:

executeReplaced {
  case class GainLife(life: Int) extends Event {
    def execute() = {
      _life += life
    }
  }
  GainLife(life)
}

Going away from my domain, the problem is basically making the command pattern nice-looking: there is one place where the command's logic belongs, and I want to declare it there. But then I have to define the command's arguments at the same place, and I struggle to make them available to code located elsewhere. If I separate the data from the logic, I can't execute the command anywhere except for where the logic is located, and it again breaks code locality.

Silly Freak
  • 4,061
  • 1
  • 36
  • 58

1 Answers1

0

You could make Event a sealed trait, this means you can only create subtypes in the same file, this way the Scala compiler will know all possible subtypes and you can pattern match on it nicely somewhere else (you still get tight coupling) and get compile errors if you have no behaviour defined for one type of event.

sealed trait Event
case class GainLife(n: Int) extends Event
case class GainScore(n: Int) extends Event

and then somewhere else

var life, score = 5
event match {
  case GainLife(n) => life += n
  case GainScore(n) => score += n
}
johanandren
  • 11,249
  • 1
  • 25
  • 30
  • this solves a problem, but not the one I'm having: I want the `life += n` local with the `gainLife` method, not local with all other events. Also, the set of events is not one I consider "known from the start", so sealed is not the best idea here. – Silly Freak Feb 10 '15 at 09:56
  • Sorry, thought you wanted to keep the modification logic close to the state and not close to the event, I guess I misunderstood your question. – johanandren Feb 10 '15 at 10:22