関数型パーサー

もうすぐ Haskell の本が出版されるので復習する。
『プログラミング Haskell』 の第8章を今回は Scala(version 2.9.2) で書く。

Scala 的に使った方がいいらしいので Option や Either も勉強する。
ScalaAPI と以下を参考にしながら書いてみた。

まずはパターンマッチで

ほとんど変わらないはずだが、書籍よりも Code の Parsing.lhs と parser.lhs の方を参考にした。

object Parsers {
  type P[A] = Seq[Char] => Option[(A,Seq[Char])]

  implicit def parserWrapper[A](p: P[A]) = new {
    def >>=[B](f: A => P[B]): P[B] = inp => parse(p, inp) match {
      case None          => None
      case Some((v,out)) => parse(f(v), out)
    }
    def +++(q: P[A]): P[A] = inp => parse(p, inp) match {
      case None => parse(q, inp)
      case x    => x
    }
  }

  def parse[A](p: P[A], inp: Seq[Char]): Option[(A,Seq[Char])] = p(inp)

  def unit   [A](v: A): P[A]    = inp => Some((v,inp))
  def failure[A]      : P[A]    = inp => None
  def item            : P[Char] = inp => inp match {
    case Seq()         => None
    case Seq(v,out@_*) => Some((v,out))
  }

  def many [A](p: P[A]): P[Seq[A]] = many1(p) +++ unit(Seq.empty)
  def many1[A](p: P[A]): P[Seq[A]] = p >>= (v => many(p) >>= (vs => unit(v+:vs)))
  def token[A](p: P[A]): P[A]      = space >>= (_ => p >>= (v => space >>= (_ => unit(v))))

  val sat: (Char => Boolean) => P[Char] =
    p => item >>= (x => if (p(x)) unit(x) else failure)
  val digit      = sat(_.isDigit)
  val lower      = sat(_.isLower)
  val upper      = sat(_.isUpper)
  val alpha      = sat(_.isLetter)
  val alphanum   = sat(_.isLetterOrDigit)
  val char: Char => P[Char] = x => sat(_ == x)
  val string: String => P[String] = {
    case "" => unit("")
    case x  => char(x.head) >>= (_ => string(x.tail) >>= (_ => unit(x)))
  }
  val ident      = lower >>= (x => many(alphanum) >>= (xs => unit(x+:xs)))
  val nat        = many1(digit) >>= (xs => unit(xs.mkString.toInt))
  val int        = (char('-') >>= (_ => nat >>= (n => unit(-n)))) +++ nat
  val space      = many(sat(_.isWhitespace)) >>= (_ => unit())
  val identifier = token(ident)
  val natural    = token(nat)
  val integer    = token(int)
  val symbol: (String => P[String]) = xs => token(string(xs))
}

import Parsers._
lazy val expr: P[Int] = term >>= (t =>
                         (symbol("+") >>= (_ =>
                          expr        >>= (e =>
                          unit(t+e)))
                         ) +++ unit(t))
lazy val term: P[Int] = fact >>= (f =>
                         (symbol("*") >>= (_ =>
                          term        >>= (t =>
                          unit(f*t)))
                         ) +++ unit(f))
lazy val fact: P[Int] = (symbol("(") >>= (_ =>
                         expr        >>= (e =>
                         symbol(")") >>= (_ =>
                         unit(e))))
                        ) +++ natural
val eval = (xs: String) => parse(expr, xs.toSeq) match {
  case Some((n, Seq())) => n
  case Some((_, out))   => sys.error("unused input " + out.mkString)
  case None             => sys.error("invalid input")
}

assert( eval("2*3+4")       == 10 )
assert( eval("2*(3+4)")     == 14 )
assert( eval("2 * (3 + 4)") == 14 )

Option のメソッドを使う

パターンマッチは一覧性にすぐれるのだけれども Option でパターンマッチする場合に決まった形が現れる。
この部分の None => None は何度も書いていると飽きてくる...多分

    def >>=[B](f: A => P[B]): P[B] = inp => parse(p, inp) match {
      case None          => None
      case Some((v,out)) => parse(f(v), out)
    }


というわけで省略してしまう。

    def >>=[B](f: A => P[B]): P[B] =
      inp => parse(p, inp).flatMap{ case (v,out) => parse(f(v), out) }

Some を書かない代わりに flatMap でつなげるだけ。


もう一つの方も x => x は Some(y) => Some(y) なので何かメソッドがありそう。

    def +++(q: P[A]): P[A] = inp => parse(p, inp) match {
      case None => parse(q, inp)
      case x    => x
    }


しかし、Option の API を探してみたがこれと思えるものがなかった。

    def +++(q: P[A])        : P[A] =
      inp => parse(p, inp).toLeft(parse[A](q, inp)).fold(Option(_), identity)

toLeft で一旦 Either に変換して再度 fold で Option に戻している。
おそらく None => None の場合は flatMap で済むのでメソッドにすればよいが、
それ以外の場合は最初から Either を使うのだろう。

Either のメソッドを使う

パーサーの戻り値を Either 型に変更する。
まずはパターンマッチで

  type P[A] = Seq[Char] => Either[Unit,(A,Seq[Char])]

  implicit def parserWrapper[A](p: P[A]) = new {
    def >>=[B](f: A => P[B]): P[B] = inp => parse(p, inp) match {
      case Left(_)        => Left()
      case Right((v,out)) => parse(f(v), out)
    }
    def +++(q: P[A]): P[A] = inp => parse(p, inp) match {
      case Left(_) => parse(q, inp)
      case x       => x
    }

None が Left に、Some が Right に変わっただけ


これを Either のメソッドを使うように変更してみる。

  implicit def parserWrapper[A](p: P[A]) = new {
    def >>=[B](f: A => P[B]): P[B] = inp =>
      parse(p, inp).right flatMap{ case (v,out) => parse(f(v), out) }
    def +++   (q: P[A])     : P[A] = inp =>
      parse(p, inp).left  flatMap{ case _       => parse(q, inp) }
  }

両方とも flatMap になるので読みやすい。
.right や .left は 「Right だったら」や「Left だったら」と読める。


パーサーを生成している部分も変更しなくてはならない。
これも素直に None を Left に Some を Right に書き換えただけ。

  def unit   [A](v: A): P[A]    = inp => Right((v,inp))
  def failure[A]      : P[A]    = inp => Left()
  def item            : P[Char] = inp => inp match {
    case Seq()         => Left()
    case Seq(v,out@_*) => Right((v,out))
  }


item は Seq でパターンマッチしているがこれもパターンマッチでなくすことができる。

  def item            : P[Char] = inp => if (inp.isEmpty) Left() else Right((inp.head,inp.tail))


さらに Either のメソッドを使って

  def item            : P[Char] = inp => Either.cond(!inp.isEmpty, (inp.head,inp.tail), ())

左が Right で右が Left なので少しややこしい。

for 式を使う

調べていたら、第8章を Scala で実装されている方がいた。


パーサーに map と flatMap が実装されていれば for 式が使えるらしい。

object Parsers {
  type P[A] = Seq[Char] => Either[Unit,(A,Seq[Char])]

  implicit def parserWrapper[A](p: P[A]) = new {
    def flatMap[B](f: A => P[B]): P[B] = inp =>
      parse(p, inp).right flatMap{ case (v,out) => parse(f(v), out) }
    def map    [B](f: A => B)   : P[B] = inp =>
      parse(p, inp).right flatMap{ case (v,out) => Right((f(v),out)) }
    def +++   (q: P[A])     : P[A] = inp =>
      parse(p, inp).left  flatMap{ case _       => parse(q, inp) }
  }

  def parse[A](p: P[A], inp: Seq[Char]) = p(inp)

  def unit   [A](v: A): P[A]    = inp => Right((v,inp))
  def failure[A]      : P[A]    = inp => Left()
  def item            : P[Char] = inp => Either.cond(!inp.isEmpty, (inp.head,inp.tail), ())

  def many [A](p: P[A]): P[Seq[A]] = many1(p) +++ unit(Seq.empty)
  def many1[A](p: P[A]): P[Seq[A]] = for (v <- p; vs <- many(p)) yield v+:vs
  def token[A](p: P[A]): P[A]      = for (_ <- space; v <- p;_ <- space) yield v

  val sat: (Char => Boolean) => P[Char] =
    p => for (x <- item; y <- if (p(x)) unit(x) else failure) yield(y)
  val digit      = sat(_.isDigit)
  val lower      = sat(_.isLower)
  val upper      = sat(_.isUpper)
  val alpha      = sat(_.isLetter)
  val alphanum   = sat(_.isLetterOrDigit)
  val char: Char => P[Char] = x => sat(_ == x)
  val string: String => P[String] = {
    case "" => unit("")
    case x  => for (_ <- char(x.head);_ <- string(x.tail)) yield x
  }
  val ident      = for (x <- lower; xs <- many(alphanum)) yield x+:xs
  val nat        = for (xs <- many1(digit)) yield xs.mkString.toInt
  val int        = (for (_ <- char('-'); n <- nat) yield -n) +++ nat
  val space      = for (_ <- many(sat(_.isWhitespace))) yield ()
  val identifier = token(ident)
  val natural    = token(nat)
  val integer    = token(int)
  val symbol: (String => P[String]) = xs => token(string(xs))
}

import Parsers._
lazy val expr: P[Int] = for {
                          t <- term
                          r <- (for {
                            _ <- symbol("+");
                            e <- expr
                          } yield t+e) +++ unit(t)
                        } yield r
lazy val term: P[Int] = for {
                          f <- fact
                          r <- (for {
                            _ <- symbol("*");
                            t <- term
                          } yield f*t) +++ unit(f)
                        } yield r
lazy val fact: P[Int] = (for {
                           _ <- symbol("(")
                           e <- expr
                           _ <- symbol(")")
                         } yield e) +++ natural
val eval = (xs: String) => parse(expr, xs.toSeq) match {
  case Right((n, Seq())) => n
  case Right((_, out))   => sys.error("unused input " + out.mkString)
  case Left(_)           => sys.error("invalid input")
}

assert( eval ("2*3+4")       == 10 )
assert( eval ("2*(3+4)")     == 14 )
assert( eval ("2 * (3 + 4)") == 14 )

flatMap だけでよさそうに思うが map がないとエラーになる。
eval でメソッド使ってないことに気づいたが、ここはパターンマッチの方が見やすいのでこのままにしておく。