Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: orElse on a component of a return value

Tags:

scala

I've been tasked with attaching an audit trail onto a bunch of calcuations for reconstruction of values after the fact (i.e. people with business domain knowledge to decipher what went wrong.) The current code looks something like this:

def doSomething = f(x) orElse g(x,y,z) orElse h(p,q,r) orElse default

Each of these returns an Option. The new code should return a tuple of (Option, Audit.)

I've implemented it as

def doSomething = f(x) match{
  case None => g_prime(x,y,z)
  case x @ Some(_) => (x, SomeAuditObject)
}
//and taking some liberties with the actual signature...
def g_prime(x,y,z) = g(x,y,z) match{

and so on until the "default." Each function chains to the next and the next and so on. I don't like it. It feels way too imperative. I'm missing something. There's some way of thinking about this problem that I'm just not seeing. Other than wrapping the return values into another Option, what is it?

like image 774
wheaties Avatar asked May 13 '26 21:05

wheaties


1 Answers

You can use Monads to compose transformations that leave an audit trail. You can compose the audits inside the Monad. Have a look at this answer for further details.

I tried to produce an example for you. I did not know how to handle the final step of the for-comprehension which is a map and provides no audit trail. If you disallow the use of map you cannot use for-comprehensions but have to use plain calls to flatMap.

case class WithAudit[A](value: A, audit: String){
  def flatMap[B](f: A => WithAudit[B]): WithAudit[B] = {
    val bWithAudit = f(value)
    WithAudit(bWithAudit.value, audit + ":" + bWithAudit.audit)
  }
  def map[B](f: A => B): WithAudit[B] = {
    WithAudit(f(value), audit +  ":applied unknown function")
  }
}

def doSomething(in: Option[Int]): WithAudit[Option[Int]] = WithAudit(
    in.map(x => x - 23),
    "substract 23"
)

def somethingElse(in: Int): WithAudit[String] = WithAudit(
  in.toString, 
  "convert to String"
)


val processed = for(
    v <- WithAudit(Some(42), "input Some(42)");
    proc <- doSomething(v);
    intVal <- WithAudit(proc.getOrElse(0), "if invalid, insert default 0");
    asString <- somethingElse(intVal)
) yield asString

println(processed)

The output will be

WithAudit(
  19,
  input Some(42)
    :substract 23
    :if invalid, insert default 0
    :convert to String
    :applied unknown function
)

Safety

Using flatMap to process the value enforces the provision of an audit. If you don't provide map and limit how you can extract the value from the monad (maybe write a log output if you do so) you can be pretty safely assume that every transformation on the value will get logged. And when the value is obtained, you'll get an entry in your log.

like image 114
ziggystar Avatar answered May 19 '26 04:05

ziggystar