安全な RPN 電卓を作ろう 

LYAHFGG:

第10章で逆ポーランド記法 (RPN) の電卓を実装せよという問題を解いたときには、この電卓は文法的に正しい入力が与えられる限り正しく動くよ、という注意書きがありました。

最初に RPN 電卓を作った章は飛ばしたけど、コードはここにあるから Scala に訳してみる:

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
         }
foldingFunction: (list: List[Double], next: String)List[Double]

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

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

動作しているみたいだ。

次に畳み込み関数がエラーを処理できるようにする。parseInt は以下のように実装できる:

scala> import scala.util.Try
import scala.util.Try

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

scala> parseInt("1")
res1: Option[Int] = Some(1)

scala> parseInt("foo")
res2: Option[Int] = None

以下が更新された畳込み関数:

scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._

scala> 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: List[Double], next: String)Option[List[Double]]

scala> foldingFunction(List(3, 2), "*")
res3: Option[List[Double]] = Some(List(6.0))

scala> foldingFunction(Nil, "*")
res4: Option[List[Double]] = None

scala> foldingFunction(Nil, "wawa")
res5: Option[List[Double]] = None

以下が foldM を用いて書いた solveRPN だ:

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

scala> solveRPN("1 2 * 4 +")
res6: Option[Double] = Some(6.0)

scala> solveRPN("1 2 * 4")
res7: Option[Double] = None

scala> solveRPN("1 8 garbage")
res8: Option[Double] = None

Contents