Archive for 4月 2011

Scala: Haskell 本の引き写しでパーサジェネレータ

プログラミング Haskell」の第 8 章「関数型パーサ」の写経をしようかと思ったのですが、そのまま引き写しても面白くないので、Scala で書いてみました。せっかくですので、Haskell のモナド連結の糖衣構文 do を、極力そのまま Scala の for 式で書けるよう、Parser[A] には、map(), flatMap() を備え、Scala の型とします。

…できた。Haskell 版から変えたところとしては、入力文字列にパーサを適用した際には、Scala らしく Option[(A, String)] を返すようにした、Haskell で [Char] であるところは、適宜 String にした、程度か。案外そのまま行けますね、というよりも、Scala が Haskell を色濃く反映しているんですね。

abstract trait Parser[A] {
  def apply(inp: String): Option[(A, String)]
  def parse(inp: String) = apply(inp)
  def map[B](f: (A) => B) = {
    def p_parse = parse _
    new Parser[B] {
      def apply(inp: String) = {
        p_parse(inp) match {
          case None => None
          case Some((v, out)) => Some((f(v), out))
        }
      }
    }
  }
  def flatMap[B](f: (A) => Parser[B]): Parser[B] = {
    def p_parse = parse _
    new Parser[B] {
      def apply(inp: String) = {
        p_parse(inp) match {
          case None => None
          case Some((v, out)) => f(v)(out)
        }
      }
    }
  }
  def +++(q: Parser[A]) = {
    def p_parse = parse _
    new Parser[A] {
      def apply(inp: String) = {
        p_parse(inp) match {
          case None => q.parse(inp)
          case s @ Some(_) => s
        }
      }
    }
  }
}

def return_[A](v: A): Parser[A] = {
  new Parser[A] { def apply(inp: String) = { Some((v, inp)) } }
}

def failure[A]: Parser[A] = {
  new Parser[A] { def apply(inp: String) = { None } }
}

def item: Parser[Char] = new Parser[Char] {
  def apply(inp: String) = inp match {
    case "" => None
    case _ => Some((inp.head, inp.tail))
  }
}

def parse[A](p: Parser[A], inp: String): Option[(A, String)] = p(inp)

/*

parse(return_(1), "abc")
res1: Option[(Int, String)] = Some((1,abc))

parse(failure, "abc")
res2: Option[(Nothing, String)] = None

parse(item, "abc")
res3: Option[(Char, String)] = Some((a,bc))

 */

/*

def p = for {
  x <- item
  _ <- item
  y <- item
} yield (x, y)
p: Parser[(Char, Char)]

def p_dash = item.flatMap(x =>
  item.flatMap(__ =>
    item.map(y =>
      (x, y) )))
p_dash: Parser[(Char, Char)]

parse(p, "abcdef")
res4: Option[((Char, Char), String)] = Some(((a,c),def))

parse(p, "ab")
res5: Option[((Char, Char), String)] = None

 */

/*

parse(item +++ return_('d'), "abc")
res6: Option[(Char, String)] = Some((a,bc))

parse(failure +++ return_('d'), "abc")
res7: Option[(Char, String)] = Some((d,abc))

parse(failure +++ failure, "abc")
res8: Option[(Nothing, String)] = None

 */

def sat(p: (Char) => Boolean): Parser[Char] = for {
  x <- item
  r <- if (p(x)) return_(x) else failure
} yield (r)

def digit = sat(_.isDigit)

def lower = sat(_.isLower)

def upper = sat(_.isUpper)

def letter = sat(_.isLetter)

def alphanum = letter +++ digit

def char(x: Char) = sat(x.==)

/*

parse(digit, "123")
res9: Option[(Char, String)] = Some((1,23))

parse(digit, "abc")
res10: Option[(Char, String)] = None

parse(char('a'), "abc")
res11: Option[(Char, String)] = Some((a,bc))

parse(char('a'), "123")
res12: Option[(Char, String)] = None

 */

// これは文字列を返すパーサとする。Scala の String は List[Char] ではない
def string(s: String): Parser[String] = s match {
  case "" => return_("")
  case _ => for {
    x <- char(s.head)
    xs <- string(s.tail)
  } yield (x +: xs)
}

/*

parse(string("abc"), "abcdef")
res13: Option[(String, String)] = Some((abc,def))

parse(string("abc"), "ab1234")
res14: Option[(String, String)] = None

 */

object Many {
  def many[A](p: Parser[A]): Parser[List[A]] = {
    many1(p) +++ return_(Nil)
  }
  def many1[A](p: Parser[A]): Parser[List[A]] = {
    for {
      v <- p
      vs <- many(p)
    } yield (v :: vs)
  }
}
import Many._

/*

parse(many(digit), "123abc")
res15: Option[(List[Char], String)] = Some((List(1, 2, 3),abc))

parse(many(digit), "abcdef")
res16: Option[(List[Char], String)] = Some((List(),abcdef))

parse(many1(digit), "abcdef")
res17: Option[(List[Char], String)] = None

 */

// 仕方ないので文字列にしておく
def ident = for {
  x <- lower
  xs <- many(alphanum)
} yield ((x :: xs).foldLeft("")(_ + _))

// これも
def nat: Parser[Int] = for {
  xs <- many1(digit)
} yield (xs.foldLeft("")(_ + _).toInt)

def space: Parser[Unit] = for {
  _ <- many(sat(_.isWhitespace))
} yield (Unit)

/*

parse(ident, "abc def")
res0: Option[(java.lang.String, String)] = Some((abc, def))

parse(nat, "123 abc")
res1: Option[(Int, String)] = Some((123, abc))

parse(space, " abc")
res2: Option[(Unit, String)] = Some(((),abc))

 */

def token[A](p: Parser[A]): Parser[A] = for {
  _ <- space
  v <- p
  _ <- space
} yield (v)

def identifier: Parser[String] = token(ident)

def natural: Parser[Int] = token(nat)

def symbol(xs: String): Parser[String] = token(string(xs))

/*

def p: Parser[List[Int]] = for {
  _ <- symbol("[")
  n <- natural
  ns <- many(
    for {
      _ <- symbol(",")
      x <- natural
    } yield (x)
  )
  _ <- symbol("]")
} yield (n :: ns)

parse(p, " [1, 2, 3] ")
res19: Option[(List[Int], String)] = Some((List(1, 2, 3),))

parse(p, "[1, 2,]")
res20: Option[(List[Int], String)] = None

 */

def int: Parser[Int] = (
  for {
    _ <- symbol("-")
    n <- natural
  } yield (- n)
) +++ natural

object Expr {
  def expr: Parser[Int] = for {
    t <- term
    r <- (
      for {
        _ <- symbol("+")
        e <- expr
      } yield (t + e)
    ) +++ (
      for {
        _ <- symbol("-")
        e <- expr
      } yield (t - e)
    ) +++ return_(t)
  } yield (r)
  def term: Parser[Int] = for {
    f <- factor
    r <- (
      for {
        _ <- symbol("*")
        t <- term
      } yield (f * t)
    ) +++ (
      for {
        _ <- symbol("/")
        t <- term
      } yield (f / t)
    ) +++ return_(f)
  } yield (r)
  def factor: Parser[Int] = (
    for {
      _ <- symbol("(")
      e <- expr
      _ <- symbol(")")
    } yield (e)
  ) +++ int
}
import Expr._

/*

parse(expr, "2*3+4")
res26: Option[(Int, String)] = Some((10,))

parse(expr, "2*(3+4)")
res28: Option[(Int, String)] = Some((14,))

parse(expr, "2 * (3 + 4)")
res30: Option[(Int, String)] = Some((14,))

parse(expr, "2*3-4")
res33: Option[(Int, String)] = Some((2,))

parse(expr, "-1")
res36: Option[(Int, String)] = Some((-1,))

parse(expr, "(-3 * 4 * - 2) / 6")
res43: Option[(Int, String)] = Some((4,))

 */



Scala: エラトステネスのふるいで無限長素数列

簡潔すぎワロタw

import Stream._
def sieve[A <% BigInt](xxs: Stream[A]): Stream[A] = xxs match {
  case p #:: xs => cons(p, sieve(xs.filter(_ % p != 0)))
}
lazy val primes = sieve(from(2))

先頭 10 個。

scala> primes.take(10).toList
res0: List[Int] = List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)

Fri Apr 22 2011: ついでに、遅延評価版のクイックソートも。まずは、普通に eager 評価のリストで書いた場合:

def qsort[A <% Ordered[A]](xxs: List[A]): List[A] = xxs match {
  case Nil => Nil
  case p :: xs =>
    qsort(xs filter(_ <= p)) ++ (p :: qsort(xs filter(p < _)))
}

ストリームで書くと、以下のように:

import Stream._
def qsort[A <% Ordered[A]](xxs: Stream[A]): Stream[A] = xxs match {
  case Empty => Empty
  case p #:: xs =>
    qsort(xs filter(_ < p)) ++ (p #:: qsort(xs filter(p <= _)))
}

ほぼ同じですが、”Empty” や “#::” などが特徴的です。不用意に無限長ストリームのソートなんかしちゃダメですよ。filter() が返ってきません。




WSH JScript の REPL を書いてみた

秀丸エディタ用、Emacs の eval-print-last-sexp 風マクロ for Scala, Clojure, Gauche, Groovy, Python and Ruby – Ayutaya.com」で、REPL が無いので流していた WSH の JScript (JavaScript) ですが、eval() があるのだからサクッと書けば良いので、どうせ世の中にはすでにたくさんの実装があるとは思われますが、書いてみました。お入り用でしたらこちらからどうぞ。

eval() が環境を汚すので、識別子には “_” を前置しています。それでも衝突すると言うならば、”_” を “_HOGEHOGE_” とでも全置換しても平気なように書かれています。ダブルクォートもバックスラッシュも使っていないので、別プログラム内へ埋め込むのも簡単です。一点ハマったのは、eval() は現在の環境内で実行されるので、当初別ファイルをロードする処理を関数にしたら、呼ばれた関数内で eval() されてしまい、そこから戻ったら “var” も “function” も消えていたことです。仕方がないので、入力ハンドラをスタックするようにしています。

実行例としては、以下のような感じです。

C:\>cscript.exe wshrepl.js
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

> 1 + 2 +
|   3 + 4
= 10
> function foo() {
|   WScript.Echo("Hello!");
| }
= undefined
> foo()
Hello!
= undefined
> :load c:\priv\prog\bat\wsh.js
= Hello, World!
= undefined
> ^Z

C:\>

以下は、ついでの tips です。

最新のブラウザの JavaScript に比べるといろいろと見劣りする Jscript ではありますが、そこは腐っても JavaScript 1.5 ですから、prototype を拡張して、map(), reduce(), filter() その他を拡張しておくと、なかなかに使える環境になります。

include が無い JScript ですが、そこは eval() で何とでもなります。以下は、実行スクリプトと同一ディレクトリにある “wsh.js” を初期化ファイルとして取り込みます。記述を短くしようと思って “WScript” を “with” すると、なぜか挙動がおかしくなります。

eval(WScript.CreateObject('Scripting.FileSystemObject').OpenTextFile(
 WScript.ScriptFullName.slice(0,-WScript.ScriptName.length)+'wsh.js' ).ReadAll());



秀丸エディタ用、Emacs の eval-print-last-sexp 風マクロ for Scala, Clojure, Gauche, Groovy, Python, Ruby, JScript, Haskell and Perl

Emacs の lisp-interaction-mode に、編集中のテキスト上のカーソル直前位置の Emacs Lisp の S 式を評価する便利なコマンド “eval-print-last-sexp” がありますが、その秀丸エディタ用、各種プログラミング言語対応版です。下記は HTML で文章を書きながら、その中の Clojure のサンプルコードを評価している例です。矢印位置にカーソル (|) がある状態で、この場合は “eval-clojure.mac ” を実行してみます。

<p>下記は、クイックソートのサンプルです:

<pre>
(defn qsort [[x & xs]]
  (when x
    (let [smaller #(< % x)]
      (lazy-cat
        (qsort (filter smaller xs))
        [x]
        (qsort (remove smaller xs)) ))))| ←
</pre>

定義が評価されてシンボルが返ります。

<p>下記は、クイックソートのサンプルです:

<pre>
(defn qsort [[x & xs]]
  (when x
    (let [smaller #(< % x)]
      (lazy-cat
        (qsort (filter smaller xs))
        [x]
        (qsort (remove smaller xs)) ))))
#'user/qsort ←
</pre>

そのまま試しに実行してみます。

(qsort '(5 3 8 9 2))|

その場に結果が返ります。

(qsort '(5 3 8 9 2))
(2 3 5 8 9)

いずれも、日本語も通ります。下記は Scala の例です。

val l = "日本語" :: "テキスト" :: Nil
l: List[java.lang.String] = List(日本語, テキスト)

どこか、昔の Basic インタプリタで開発をしていたような快適さがあります (あいにく SmallTalk の経験は無い)。とりわけ、関数型言語と相性が良いんでないかしら、このスタイルは。メリットとしては、以下のような感じかと思います:

  • 言語をその場でチャンポンに書ける: ガッツリとアプリを開発するのであれば Emacs なり Eclipse なりの各言語のモードを利用すれば良いのでしょうが、つらつらと考えながら各言語を横断的に、サンプルなどのスクリプト片を次々に試しながらコメントも書きたい場合などには、同一テキスト上でその場で評価し、すぐに結果が返る方が快適です
  • コピペをしなくてすむ: いちいちエディタ上で書いて、それを REPL にコピペ実行、というのは、思考を阻害します
  • 起動オーバーヘッドが無い: REPL のセッションをバックグラウンドで維持しますので、とりわけ Scala や Clojure などの、JVM を使う、起動が異様に遅い REPL でも、2 度目以降の評価はサクサクです。非力なノートマシンには、これがありがたい
  • エディタから離れなくて済む: 一日のほとんどをエディタ上で過ごす身としては、ひととおりのスクリプト実行がその場でできるというのはありがたいです。コード片も、無くさず記録として残りますし
  • キー一発: キーを割り当てておけば、普段書きのテキストから各言語にキー一発 (2 ストロークならば二発ですが) でアクセスできます

秀丸エディタは、ver .8.0 以降のマクロで COM オブジェクトの操作をサポートしたため、以前にはできなかった複雑なアプリケーション連携や、テキストを開いている間だけ永続するオブジェクト等を持つことができるようになりました。今回のマクロも、バックグラウンドで各言語の REPL セッションを起動して、テキストをクローズするまでの間、状態を維持することができるようになったことで実現できました。

詳細は以下ですが、詳しくはコードを読んでください。なお、範囲選択をして実行すれば、選択範囲を評価します。

マクロ名 カーソル以前のどこまでを一単位として評価? 入出力方式 既知の問題
eval-scala.mac カーソルのある行、あるいは対応する括弧かヒアドキュメントによって継続している複数行 (Scala に “\” による行継続は無い) 入力は REPL の :load、出力は STDOUT (SJIS) あいかわらず初回起動が遅いが、こればかりはどうしようもない
eval-clojure.mac カーソル直前の S 式 入力は専用 REPL、出力は STDOUT (SJIS) 初回起動は、そこそこに遅い。STDERR をファイルに出力する方法が分からなかったが、多分あまり支障はない
eval-gauche.mac カーソル直前の S 式 入出力とも専用 REPL (UTF-8) 特に無い。Scheme は Gauche がお気に入りなので、Guile は確認していない。
eval-groovy.mac カーソルのある行、あるいは対応する括弧かヒアドキュメントによって継続している複数行、あるいは “\” で継続している複数行 入力は REPL の \l、出力は STDOUT (SJIS) jline が “unix” モードでないと STDIN を正しく扱えなかったため、動作に MinGW の sh(1) と stty(1) が必要
eval-python.mac 第一カラムが空白ではないカーソル行、あるいはカーソル行以前で第一カラムが空白ではない行までの複数行、あるいは “\” で行継続をしている複数行 入力は専用の REPL、出力は STDOUT (UTF-8) REPL の返事が淡白すぎて、返り値が分かりづらい
eval-ruby.mac カーソルのある行、あるいはカーソル行上の “end” とカラムが合う直前の開始行までの複数行、あるいは “\” で行継続している複数行 入力は REPL の irb_source()、出力は STDOUT (SJIS) irb_source() が、たまにファイルハンドルを離してくれないのか、一時ファイルを delete できないことがある。上書きするので、おそらく動作に支障はない
eval-jscript.mac Groovy とだいたい同じ (本当なら、いろいろ違うんだけど…) 入力は自前実装の REPL で “:load”、出力は STDOUT (SJIS) :load は、通常の eval() ではなく、REPL 的に行単位で評価されるので注意。ブロック内の空行はインデントしておいてください
eval-haskell.mac 第 1 カラム目が空白であれば、直前の “module” までをモジュールとしてロードする。第 1 カラム目が空白でなければ、式として解釈する モジュールの入力は REPL の “:load”、式の入力は STDIN から、出力は STDOUT GHC では、日本語入力は Unicode に落ちるものの、出力はできないようだ
eval-perl.mac カーソル行か、括弧を辿って戻れるところまで 入力は自前 REPL の “:load” から、出力は STDOUT から Perl の eval() は新しくブロックを作ってしまうので、評価単位内の “my”, “local” 変数や “$1″ などは、eval() が終わると破棄されてしまう。”$s =~ /([a-z]+)/; $mstr = $1″ のように複文で保持するなど要工夫

あとは Haskell (トップレベルの扱いがよく分からなくて作れなかった) と、JavaScript (WSH には REPL が無い) 用が欲しいところです。

Wed Apr 13 2011: JScript 用を追加。REPL 単体については「WSH JScript の REPL を書いてみた – Ayutaya.com」から。

Fri Apr 15 2011: Haskell (GHC) 用を追加。言語が言語なので、少々特殊。

このカーソル位置(↓)でマクロを起動すると、

module Test where
  fib 1 = 1
  fib 2 = 1
  fib n = fib (n - 1) + fib (n - 2)
|

モジュールをロード。もう一度実行すれば、再ロード。

module Test where
  fib 1 = 1
  fib 2 = 1
  fib n = fib (n - 1) + fib (n - 2)

[1 of 1] Compiling Test
( C:\DOCUME~1\KIICHI~1\LOCALS~1\Temp\haskell_input_2821200.hs, interpreted )
Ok, modules loaded: Test.
|

続けて式を評価すると、

fib 10

値が返る。

fib 10
55|

Sun Apr 17 2011: Perl を追加。eval() の仕様故に、若干心残りが…。残るは PHP と VBScript くらいか。




Scala: コンストラクタの意味合いとクロージャ

以前、「Scala: コンストラクタ中の一時オブジェクトに消えて欲しい – Ayutaya.com」などと言っていた私ですが、ちょっと考え違いをしていました。なまじ逆コンパイルなどをしてしまったために、C++~Java 的な構造体指向の発想にとらわれていましたが、文法的に見ると、Scala のクラス~コンストラクタは、静的なクロージャセットの構築だと見た方が自然です。であれば、クロージャに束縛された「スタック上の変数」が残るのも道理です。

例として、ベクトル型 (+ 絶対値を求めるメソッド) を Scala で下記のように書いたとして、

class Vect(x: Int, y: Int) {
  def abs = {
    math.sqrt(x * x + y * y)
  }
}

val v = new Vect(2, 3)
println(v.abs)

同じものを、例えば JavaScript で、プロトタイプを使わずにクロージャで書いたとします。

function Vect(x, y) {
  this.abs = function() {
    return (Math.sqrt(x * x + y * y));
  }
}

var v = new Vect(2, 3);
WScript.Echo(v.abs());

一目瞭然でそっくりです。Scala でオブジェクト生成の際に “new” が省略されないことも、それが意味するところが、通常の関数呼び出しと違って、クロージャの配列領域を確保する命令であると考えることができますし (上記の JavaScript でも “new” が、”this” に相当するオブジェクトを作っていますし)、パブリックな「メンバ変数」が実際にはアクセサとなっているところも、Scala のコンストラクタが「クロージャ群の構築子」であると考えれば一貫しています。

以上、Scala で腑に落ちた点をちょろっと。では。