プログラミングHaskell 8.4 連結

ひきつづきプログラミングHaskellScalaで書き換えつつ読んでいきます。

おそらく、二つのパーサーを組み合わせる最も単純な方法は、一方の後に他方を適用することだろう。一番目のパーサーの返す出力文字列が、二番目のパーサーの入力文字列となる。 連結演算子>>=(そして)を用いて、結果を処理する形でパーサーを連結させる方が経験上便利だ。

これは、flatMapのシグネチャだ!

//  def >>=[A, B]: Parser[A] => Parser[B] => Parser[(A, B)]
def >>=[A, B](p: Parser[A])(f: A => Parser[B]): Parser[B] = (inp: String) =>
  parse(p)(inp) match {
    case List() => List()
    case List((v, out)) => parse(f(v))(out)
  }

とここから、本の中ではdo記法を使っていくわけなのですが、ここまでの状態でfor文を書くと、以下のようになりますが、

// 動かない
def p = for {
    x <- item
    _ <- item
    y <- item
  } yield (x, y)
Error:(50, 10) value flatMap is not a member of Parsers.Parser[Char]
    x <- item

Parser型にflatMapやmapを定義していないためにコンパイルできません。ここではParserはtypeで宣言しただけであって、それにflatMapをもたせる....?よくわからないけど、FPinScalaで見たことのある方法で可能そう....!(後ほど書いてあります)。

ここではfor式を展開したバージョンを書いて、本についていきます。

// ずるいけど、mapを書きます
def map[A, B](p: Parser[A])(f: A => B): Parser[B] = (inp: String) =>
    parse(p)(inp) match {
      case Seq() => Seq()
      case Seq((v, out)) => Seq((f(v), out))
    }

def p: Parser[(Char, Char)] =
  >>=(item)((c: Char) =>
    >>=(item)((_: Char) =>
      map(item)((ccc: Char) => (c, ccc))
    )
  )

以下のコードで実行します。

object Main extends App {
  ....
  println("sample5: " + parse(p)("abcdef"))
}
sample5: List(((a,c),def))

確かに答えが得られています。何が起こっているんだ・・・・当てられる文字列の情報(今回の実行であれば"abcdef")がpというメソッドでは一切でてこないのに、うまくいっている...?いつ消費されて、いつ返されているんだ。。。Stateモナドと呼ばれる類のやつなんだろうけど、不思議すぎる〜〜〜どうなってるか、を考えずに、flatMap(>>=)では、次に渡されているんだと無心で思って次に進んでいきたいと思います。


ちなみに、Scalaで上のやつをfor式で書けるようにするには以下のようにします。ParserはParserOpsへ自動的(暗黙的に)変換され、ParserOpsが持っているメソッドを使うことができます。定義はParsersオブジェクトに配置して、それらの定義をParserOpsから呼び出します。(説明あってるカナ)

object Parsers { self =>
  type Parser[T] = (String) => Seq[(T, String)]
  ....
  // 上で書いた map とか >>= 書く
  ....
  implicit def parserOps[T](p: Parser[T]) = ParserOps(p)
  case class ParserOps[T](p: Parser[T]) {
    def flatMap[S](f: T => Parser[S]): Parser[S] =
      self.>>=(p)(f)
    def map[S](f: T => S): Parser[S] =
      self.map(p)(f)
  }
  
  def pp: Parser[(Char, Char)] = for {
    x <- item
    _ <- item
    y <- item
  } yield(x, y)
}

以下のコードで実行。

object Main extends App {
  ....
  println("sample6: " + parse(pp)("abcdef"))
}
sample6: List(((a,c),def))

今日のところまで。