Making a safe RPN calculator 

LYAHFGG:

When we were solving the problem of implementing a RPN calculator, we noted that it worked fine as long as the input that it got made sense.

We have not covered the chapter on RPN calculator, so let’s translate it into Scala.

def foldingFunction(list: List[Double], next: String): List[Double] =
  (list, next) match {
    case (x :: y :: ys, "*") => (y * x) :: ys
    case (x :: y :: ys, "+") => (y + x) :: ys
    case (x :: y :: ys, "-") => (y - x) :: ys
    case (xs, numString) => numString.toInt :: xs
  }

def solveRPN(s: String): Double =
  (s.split(' ').toList
    .foldLeft(Nil: List[Double]) {foldingFunction}).head

solveRPN("10 4 3 + 2 * -")
// res0: Double = -4.0

Looks like it’s working.

The next step is to change the folding function to handle errors gracefully. We can implement parseInt as follows:

import scala.util.Try

def parseInt(x: String): Option[Int] =
  (scala.util.Try(x.toInt) map { Some(_) }
  recover { case _: NumberFormatException => None }).get

parseInt("1")
// res2: Option[Int] = Some(value = 1)

parseInt("foo")
// res3: Option[Int] = None

Here’s the updated folding function:

import cats._, cats.syntax.all._

def foldingFunction(list: List[Double], next: String): Option[List[Double]] =
  (list, next) match {
    case (x :: y :: ys, "*") => ((y * x) :: ys).some
    case (x :: y :: ys, "+") => ((y + x) :: ys).some
    case (x :: y :: ys, "-") => ((y - x) :: ys).some
    case (xs, numString) => parseInt(numString) map {_ :: xs}
  }

foldingFunction(List(3, 2), "*")
// res4: Option[List[Double]] = Some(value = List(6.0))

foldingFunction(Nil, "*")
// res5: Option[List[Double]] = None

foldingFunction(Nil, "wawa")
// res6: Option[List[Double]] = None

Finally, here’s the updated solveRPN using foldM:

def solveRPN(s: String): Option[Double] =
  for {
    List(x) <- (Foldable[List].foldM(s.split(' ').toList,
                  Nil: List[Double]) {foldingFunction})
  } yield x

solveRPN("1 2 * 4 +")
// res7: Option[Double] = Some(value = 6.0)

solveRPN("1 2 * 4")
// res8: Option[Double] = None

solveRPN("1 8 garbage")
// res9: Option[Double] = None