1

I have a situation where I am using functions to model rule applications, with each function returning the actions it would take when applied, or, if the rule cannot be applied, the empty list. I have a number of rules that I would like to try in sequence and short-circuit. In other languages I am used to, I would treat the empty sequence as false/None and chain them with orElse, like this:


def ruleOne(): Seq[Action] = ???
def ruleTwo(): Seq[Action] = ???
def ruleThree(): Seq[Action] = ???

def applyRules(): Seq[Action] = ruleOne().orElse(ruleTwo).orElse(ruleThree)

However, as I understand the situation, this will not work and will, in fact, do something other than what I expect.

I could use return which feels bad to me, or, even worse, nested if statements. if let would have been great here, but AFAICT Scala does not have that.

What is the idiomatic approach here?

  • Write an extension method `ifNonEmpty` on `Seq[T]` and write `ruleOne().ifNonEmpty(ruleTwo).ifNonEmpty(ruleThree)`. See e.g. https://www.wix.engineering/post/scala-extension-methods-via-implicit-classes for Scala 2 and https://docs.scala-lang.org/scala3/book/ca-extension-methods.html for Scala 3. – Alexey Romanov Jan 17 '22 at 08:25
  • I would rather change the domain model to `Option[NonEmptyList[Action]]` – Luis Miguel Mejía Suárez Jan 17 '22 at 15:21
  • That's what I would have done if I'd known about `NonEmptyList`. Or is that just a cats thing? My whole reason for not going with `Option` is that it does not capture the fact that the list in the case of `Some` is never empty. – amandasystems Jan 18 '22 at 16:45

1 Answers1

6

You have different approaches here. One of them is combining all the actions inside a Seq (so creating a Seq[Seq[Action]]) and then using find (it will return the first element that matches a given condition). So, for instance:

Seq(ruleOne, ruleTwo, ruleThree).find(_.nonEmpty).getOrElse(Seq.empty[Action])

I do not know clearly your domain application, but the last getOrElse allows to convert the Option produced by the find method in a Seq. This method though eval all the sequences (no short circuit).

Another approach consists in enriching Seq with a method that simulated your idea of orElse using pimp my library/extensions method:

implicit class RichSeq[T](left: Seq[T]) {
   def or(right: => Seq[T]): Seq[T] = if(left.isEmpty) { right } else { left }
}

The by name parameter enable short circuit evaluation. Indeed, the right sequence is computed only if the left sequence is empty.

Scala 3 has a better syntax to this kind of abstraction:

extension[T](left: Seq[T]){
  def or(rigth: => Seq[T]): Seq[T] = if(left.nonEmpty) { left } else { rigth }
}

In this way, you can call:

ruleOne or ruleTwo or ruleThree

Scastie for scala 2

Scastie for scala 3

gianluca aguzzi
  • 1,734
  • 1
  • 10
  • 22