3

Is there an idiomatic solution to applying a series of business rules, for example, from an incoming JSON request. The "traditional" Java approach is very if-then intense, and Scala must offer a far better solution.

I've experimented a bit with pattern matching but haven't really come up with a pattern that works well. (Invariably, I end up with absurdly nested match statements)...

Here's an absurdly simple example of what I'm trying to do:

if (dateTime.isDefined) {
    if (d == None)
        // valid, continue
    if (d.getMillis > new DateTime().getMillis)
        // invalid, fail w/ date format message
    else
    if (d.getMillis < new DateTime(1970).getMillis)
        // invalid, fail w/ date format message
    else
        // valid, continue
} else
    // valid, continue

if (nextItem.isDefined) {
    // ...
}

I'm thinking perhaps an approach that uses a series of chained Try()... but it seems like this pattern must exist out there already.

Zaphod
  • 1,387
  • 2
  • 17
  • 33
  • if you wouldn't find any ready solution - you could wright [scala-test-like](http://scalatest.org/) DSL with asserts. Talking about JSON - there is plenty of [JSON validators](http://stackoverflow.com/a/27958096/1809978) – dk14 May 23 '15 at 19:47

2 Answers2

1

Do you mean something like this?

def test(dateTime: Option[DateTime], nextItem: Option[String]) {
  (dateTime, nextItem) match {
    case (Some(time), _) if time.getMillis > new DateTime().getMillis =>
      //do something
    case (Some(time), _) if time.getMillis > new DateTime(1970).getMillis =>
      //do something
    case (Some(time), _) =>
      //do something else
    case (None, Some(next)) =>
      //do something else
  }
}

and of course you can also use for comprehensions with options

val dateTime: Option[DateTime] =
  for {
    date <- getDate()
    time <- getTime()
  } yield new DateTime(date, time)

test(dateTime, nextItem)

There are many ways to chain trys Here is one

def validate[A](elem: A): Try[Unit] = {
  ???
}

def test[A](thingsToValidate: Iterable[A]): Try[Unit] = {
  thingsToValidate.view // view makes the whole thing lazy
    .map {              // so we don't validate more than necessary
      case elem: String => validateString(elem)
      case elem: Int => validateInt(elem)
    }.find(_.isFailure)
    .getOrElse(Success(Unit))
}

and if the number of things is fixed you could also use a for comprehension

def test(a: String, b: String, c: String): Try[Unit] = {
  for {
    _ <- validate(a)
    _ <- validate(b)
    _ <- validate(c)
  } yield(Unit)
}

or use exceptions

def test(a: String, b: String, c: String): Try[Unit] = {
  try {
    validate(a).get
    validate(b).get
    validate(c).get
    Success(Unit)
  } catch {
    case e: Throwable => Failure(e)
  } 
}
Tesseract
  • 8,049
  • 2
  • 20
  • 37
  • So the problem with this approach is the chaining... let's say I have ten different things to validate. What I want to do is call a method, have it progress through the set, and ultimately return a success/fail. That's why I'm thinking of chaining a series of `Try()`, which I think might work nicely. Basically I need a chain of `N` validations, where any failure throws back a failure, but otherwise we progress through the entire chain and return success. – Zaphod May 23 '15 at 20:37
  • I like the direction you're heading with the Try() validations... works well when we don't know how many items there are to validate. I could then extend that to make a fairly generic set of validators for different data types, and automatically feed all the JSON input through them. May also want to consider an approach that uses Scala's Json validation too. – Zaphod May 23 '15 at 22:16
  • glad, you like the approach. btw. I edited my example to include pattern matching inside the map operation. – Tesseract May 23 '15 at 22:41
0

There are many ways to do that. Depending on the conditions and actions to take there are different design patterns to follow.

For the outline you sketched in your example, pattern matching would probably be the best way to rewrite your code.

The problem in your question is that there is no real idiomatic equivalent in Scala. You need to approach the problem in a total different way because evaluating a series of conditions in that way is considered inefficient and blunt in Scala.

Chobeat
  • 3,445
  • 6
  • 41
  • 59