[Scala]Scalaでeval

Rubyのように文字列をScalaスクリプトとしてプログラム内でevalする方法。

scala-compiler.jarの中にscalaインタプリタが使っているクラスInterpreter(そのまんま)があるので、これでevalできる。

package jp.hogehoge

import scala.tools.nsc._
import scala.util.DynamicVariable

class Evaluator {
  val i = new Interpreter(new Settings(null))

  def eval(line: String): Any = {
    i.interpret("jp.hogehoge.Evaluator.result.value = " + line)
    Evaluator.result.value
  }
}

object Evaluator {
  val result = new DynamicVariable[Any](())
}

object EvalTest {
  def main(args: Array[String]): Unit = {
    val e = new Evaluator
    val r = e.eval("jp.hogehoge.Hoge.somefunc")
    println(r)
  }
}

object Hoge {
  def somefunc = "mogemoge"
}

解説

  • Interpreter::interpretは式の評価値を返してくれない。
    • なので、グローバルな変数Evaluator.resultに一旦結果を代入するコードをeval文字列の中に追加する。
    • Evaluatorがマルチスレッドで使われる時に備えて、Evaluator.resultをDynamicVariableにしておく。
  • 注意点
    • ひとつinterpretするだけで平均30ミリ秒くらいはかかるので、ループの中でぶん回すのはやめたほうがいいと思う。
    • クラスパスにあるものは何でも実行できる諸刃の剣なので、サニタイズを忘れずに。

[Scala][XML]ScalaでXMLストリームパーザを書いてみた

巨大なXMLファイルを処理するとき、プロセスのメモリにDOMを全部置いたりするのは非常に効率が悪い。なので、世の中にはSAXというイベントベースのAPIがあるわけだが、遅延評価を利用してこれをイベントベースの処理をDOMのストリームに変換してみたい。

標準ではsacla.xml.pull.XMLEventReader*1というのが既にあるが、スレッドの扱いがちょっと不満だったので、Scalaの練習がてら、自分で書いてみた。

StAXのイベントストリームから、NodeSeq(子要素を遅延評価できるようにしてある)に変換するのが、下記のXMLStreamParser。

ソース

import scala.io.Source
import scala.concurrent.SyncVar
import scala.xml._
import scala.xml.parsing._
import scala.xml.pull._

object XMLStreamParser {
  def apply(input: Source) = nodeStream(eventStream(input))

  def nodeStream(evs: Stream[XMLEvent]): Stream[Node] = {
    if (evs.isEmpty) Stream.empty
    else {
      evs.head match {
        case EvElemStart(p, l, a, s) => Stream.cons(Elem(p, l, a, s, nodeStream(evs.tail):_*),
                                                    nodeStream(findNextSibling(evs.tail, 0)))
        case EvElemEnd(p, l) => Stream.empty
        case EvText(t) => Stream.cons(Text(t), nodeStream(evs.tail))
        case EvEntityRef(e) => Stream.cons(EntityRef(e), nodeStream(evs.tail))
        case EvComment(c) => Stream.cons(Comment(c), nodeStream(evs.tail))
        case EvProcInstr(t, x) => Stream.cons(ProcInstr(t, x), nodeStream(evs.tail))
      }
    }
  }

  private def findNextSibling(evs: Stream[XMLEvent], depth: Int): Stream[XMLEvent] = {
    if (evs.isEmpty) Stream.empty
    else {
      evs.head match {
        case EvElemStart(_,_,_,_) => findNextSibling(evs.tail, depth + 1)
        case EvElemEnd(_,_) => if (depth < 1) evs.tail
                               else findNextSibling(evs.tail, depth - 1)
        case _ => findNextSibling(evs.tail, depth)
      }
    }
  }

  def eventStream(input: Source): Stream[XMLEvent] = {
    import javax.xml.stream._
    import javax.xml.stream.XMLStreamConstants._
    val factory = XMLInputFactory.newInstance()
    val reader = factory.createXMLStreamReader(new SourceReader(input))
    def st: Stream[XMLEvent] = {
      if (reader.hasNext) {
        reader.next
        reader.getEventType match {
          case START_ELEMENT =>
            Stream.cons(EvElemStart(reader.getPrefix,
                                    reader.getLocalName,
                                    reader2metadata(reader),
                                    reader2namespacebinding(reader)),
                        st)
          case END_ELEMENT =>
            Stream.cons(EvElemEnd(reader.getPrefix, reader.getLocalName), st)
          case CDATA =>
            Stream.cons(EvText(reader.getText), st)
          case CHARACTERS =>
            Stream.cons(EvText(reader.getText), st)
          case COMMENT =>
            Stream.cons(EvComment(reader.getText), st)
          case ENTITY_REFERENCE =>
            Stream.cons(EvEntityRef(reader.getLocalName), st)
          case PROCESSING_INSTRUCTION =>
            Stream.cons(EvProcInstr(reader.getPITarget, reader.getPIData), st)
          case _:Int => st
        }
      } else {
        Stream.empty
      }
    }
    st
  }

  private def reader2metadata(reader: javax.xml.stream.XMLStreamReader) = {
    var md: MetaData = Null
    for (i <- 0 until reader.getAttributeCount()) {
      if (reader.getPrefix() == null) {
        md = new UnprefixedAttribute(reader.getAttributeLocalName(i),
                                     reader.getAttributeValue(i),
                                     md)
      } else {
        md = new PrefixedAttribute(reader.getAttributePrefix(i),
                                   reader.getAttributeLocalName(i),
                                   reader.getAttributeValue(i),
                                   md)
      }
    }
    md
  }

  private def reader2namespacebinding(reader: javax.xml.stream.XMLStreamReader) = {
    var nb: NamespaceBinding = TopScope
    for (i <- 0 until reader.getNamespaceCount()) {
      nb = new NamespaceBinding(reader.getNamespacePrefix(i), reader.getNamespaceURI(i), nb)
    }
    nb
  }
}

class SourceReader(chars: Iterator[Char]) extends java.io.Reader {
  var currentChars = chars

  override def read(buf: Array[Char], off: Int, len: Int): Int = {
    if (!chars.hasNext) return -1
    var i = off
    val end = off + len
    while (i < end) {
      if (chars.hasNext) buf(i) = chars.next
      else return i - off
      i += 1
    }
    i - off
  }

  override def close() = {
    currentChars = Iterator.empty
  }
}

使い方

も帰ってきたストリームの先頭をvalやvarで宣言した変数に入れてはいけない。ストリームの全要素がメモリ上に乗ってしまうので、XMLStreamParserを使う意味がない。

ストリームの先頭は、必要なくなったらすぐに捨てるように書く。

import scala.io._
import scala.xml._
import org.scalatest._
import org.scalatest.matchers._

class XMLTest extends Spec with ShouldMatchers {

  describe("Simple XML") {
    it("001") {
      def xr = XMLStreamParser(Source.fromString("<hoge/>"))
      xr.toList should equal (List(<hoge/>))
    }

    it("002") {
      def xr = XMLStreamParser(Source.fromString("""<?xml version="1.0" encoding="UTF-8" ?>
<hoge><piyo t="0"><moko/></piyo><puyo><ketaketa/></puyo></hoge>"""))
      xr.head.asInstanceOf[Elem].child(0) should equal (<piyo t="0"><moko/></piyo>)
      xr.toList should equal (List(<hoge><piyo t="0"><moko/></piyo><puyo><ketaketa/></puyo></hoge>))
    }

    it("003") {
      def xr = XMLStreamParser(Source.fromString("<hoge>piyopiyo</hoge>"))
      xr.toList should equal (List(<hoge>piyopiyo</hoge>))
    }

    it("004") {
      def xr = XMLStreamParser(Source.fromURL("http://d.hatena.ne.jp/t2ru/rss"))
      xr.filter(_.isInstanceOf[Elem]).map(_.asInstanceOf[Elem]).head.child
      xr.toList
    }
  }
}

object XMLTestRunner {
  def main(args: Array[String]) = {
    (new XMLTest).execute()
  }
}

*1:公式でもまだ不安定なAPIらしいけど。。。

Javaに対するScalaのメリット

Javaから他の言語に行きたいという悩み

  • Javaのいけてない書き方には正直うんざりしてる。
  • でもRubyに移行する勇気はない。
    • 静的な型チェックがないのは実行時のバグが怖い。
      • 特にウォーターフォールで開発をやってるところにとっては、バグの刈り取りを後工程に先送りすることになるので、動的な言語はいまいち。
      • もっさい開発プロジェクトに、テストファースト開発を額面どおりやることなんて絶対無理です。
  • LispHaskellをやりたいけど、「こんなのをかける天才もいるんだね。俺には関係ねーや。」みたいな人が多い。
  • 今まで書いたJavaのコードが無駄になるのはいやだ。

Scalaの解決策

  • Javaよりもはるかに簡潔に書ける。
  • 静的な型チェック(Javaよりもある意味厳密)をしっかりやってくれる。
  • 最悪、Javaのコードの書き方をそのままScalaの文法に変換するだけでいいので、Javaプログラマにとっての敷居は低い。
  • Javaとの親和性が高い。というか、環境はJavaそのもの。
    • コンパイルしたらJavaのクラスファイルができる。
    • 既存のJavaコードベースとそのまま相互に呼び出せるので、徐々に移行できる。

Scalaの便利なとこ(その1):getter/setterのうんざりを解消する

自分でJavaのプログラムを書いていて一番いらいらするのはgetter/setterの定義。
冗長で見るに耐えない。

Sunが気を利かせて、ライン数の水増し手法を提供してくれたのか?(笑)

ScalaJavaに対するメリットの中では一番くだらないんじゃないかとも思いますが、意外と重要でしかもわかりやすいメリットだと思います。

Java
public class Person {

    private final String name = null; // Javaの慣習では、フィールドに直接アクセスしてはいけないことになっています。
    private final Date birth = null;
    private String phone = null;
    private String address = null;

    public Person(String name, Date birth, String phone, String address) {
        this.name = name;
        this.birth = birth;
        Util.checkPhoneIsValid(phone)
        this.phone = phone;
        this. address = address;
    }

    // 以下、getter, setterの嵐です。

    public String getName() {
        return name;
    } 

    public Date getBirth() {
        return birth;
    } 

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) throws InvalidPhoneNumberException {
        // おっと、電話番号は妥当性をチェックする必要があるんでした。
        Util.checkPhoneIsValid(phone);
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = addressn;
    }
}

...

// Personを使う側
Person p = new Person("Hogera Hogeo", new Date(), "090-0000-0000", "Tokyo");
...
return p.getPhone();
...
// ほげ男くんが携帯電話を買い換えたようです。
p.setPhone("090-1111-1111");
Scala

Scalaだとすっきりしますね。

class Person(val name: String,       // コンストラクタ引数です。
             val birth: Date,        // valやvarをつけると、メンバ変数が自動的に定義されます。
             var phone: String,      // valなら同名のgetterも自動的にできます。
             var address: String) {  // varならsetterも"(名前)_="という名前で自動的にできます。

    // インスタンスの初期化は、クラス直下でやります。
    Util.checkPhoneIsValid(phone)
    Util.checkAddressIsValid(address)

    // 自動的にできたsetterを自前のものに置き換えます。
    // (Javaのようにthrowsを書く必要はありません。)
    def phone_=(phone: String) = {
        Util.checkPhoneIsValid(phone)
        this.phone = phone
    }
}
// このクラスには、Person#name, Person#birth, Person#phone, Person#address, Person#addres_=(String)
// というメソッドがこっそり定義されています。

...

// Personを使う側
val p = new Person("Hogera Hogeo", new Date(), "090-0000-0000", "Tokyo")
...
// フィールドを参照しているように見えますが、実はp.phoneというメソッド呼び出しです。
return p.phone
...
// フィールドに代入しているように見えますが、実はp.phone_=(String)というメソッドの呼び出しです。
p.phone = "090-1111-1111"

...(その2以降は。。。)

見送られそうなJava 7のクロージャ

クロージャがJava 7に入らないかもしれないと聞いてテンションが下がった。

Javaでクロージャを実装する場合、ローカル変数のエクステントが問題になることを、以前のエントリで述べたが、候補となっていた仕様ではどうなっていたのだろう?
http://d.hatena.ne.jp/t2ru/20090115/1232038829

答えはここにあった。
http://journal.mycom.co.jp/column/jsr/055/index.html

変数をスタック上に置くか、無限エクステントな別の領域に置くかを識別するには、変数の宣言に「@Shared」をつけて行うようだ。

スコープとエクステントまとめ

ABCLのソースを読んでるうちにスコープとエクステントについてだんだんわかってきたので、自分なりに少しまとめてみた。合ってるかどうかはわからないので、すべてに?がついていると考えてください。

スコープ
アクセス可能な場所的範囲
エクステント
アクセス可能な時間的範囲
言語 種別 スコープ エクステント コンパイラインタプリタでの検索方法(典型例) 実装上の領域(典型例)
C ローカル(auto)変数 ブロック内 ブロックに入って時点から出た時点まで 関数の変数宣言リスト スタック
C ブロック内のstatic変数 ブロック内 プログラムの開始から終了まで 関数の変数宣言リスト データ領域
C トップレベルのstatic変数 ファイル内 プログラムの開始から終了まで コンパイル単位の変数宣言リスト データ領域
C グローバル変数 ファイル内、extern宣言により別ファイルの変数を参照可 プログラムの開始から終了まで コンパイル単位の変数宣言リスト データ領域(シンボルexportあり)
Java ローカル変数 ブロック内(内側のブロックで上書き可) ブロックに入って時点から出た時点まで ブロックの変数宣言リスト スタック
Java インスタンス変数 アクセス修飾子で指定した範囲 インスタンスの生成から消滅(GCに回収される)まで インスタンスフィールドのリスト ヒープ領域
Java クラス変数 アクセス修飾子で指定した範囲 クラスロード時からプログラム終了時 クラスフィールドのリスト ヒープ領域
Java TLS 保持するThreadLocalオブジェクトを参照できる範囲 最初に定義されてからスレッド消滅まで ユーザがプログラムで記述する スレッドローカル領域
Common Lisp 変数 定義されたS式内(内側のS式で上書き可) ブロックに入ってからGCに回収されるまで レキシカル環境ツリー ヒープ領域
Common Lisp 特殊変数 定義されたS式内(内側のS式で上書き可) 最初に定義されてからプログラム終了まで 特殊変数バインドのツリー*1 ヒープ領域
Ruby ローカル変数 宣言位置からブロックの終わりまで 最初に定義されてからGCに回収されるまで レキシカル環境ツリー?? ヒープ領域
Ruby インスタンス変数 クラス定義内 インスタンスの生成から消滅(GCに回収される)まで インスタンス変数リスト ヒープ領域
Ruby クラス変数 クラス定義内 クラスの定義からプログラム終了まで クラス変数リスト ヒープ領域
Ruby グローバル変数 プログラム全体 最初に定義されてからプログラム終了まで グローバル変数専用リスト ヒープ領域

クロージャを実装するときに必要とされる「無限エクステント」の変数は、必要なくなるまでメモリ上に居ないといけない(ブロックから抜けたときに勝手に消えたりしてはいけない)ので、スタック領域ではだめで、ヒープ領域においておく必要がある。

Javaのローカル変数は、スタック上にあることになっているので、インナークラスでクロージャを素直に書くことはできない。下記がその例。

// 現行のJavaでクロージャができそうでできない例
public class Test {
    Runnable test() {
        int x = 0; // ←これはダメ。下記の理由でこれはfinalである必要がある。
        return new Runnable() {
            void run() {
                System.out.println("" + x);
                x++; // ←これはダメ。外側で宣言されたxの領域は、メソッドtest()が返った時点でなくなる。
                     // Runnableオブジェクトがnewされる時点で、xの値が定数になっている必要がある。
                     // 従って、外側ではfinal宣言されていなければならない。
            }
        }
    }
}

上記のソースコードを実装したければ、変数xをスタック領域以外の場所に置かなければならない。

Common LispRubyだと、xがヒープ(?)にあるため、testが返ってもxの領域は残っている。

(defun test ()
  (let ((x 0))
     (lambda ()
        (format t "~A~%" x)
        (incf x))))

クロージャではない例だが、このstatic変数xの生存期間はプログラムの終了までなので、「無限エクステント」というのかもしれない。しかし、上記Common Lispの例の中のxがtestが呼び出されるたびに新たな領域に作成されるのに対し、下記Cの例のxは常に同じメモリを指しているので、本質的に別物だ。

int *test(void) {
    static int x = 0;
    return &x;
}

*1:シングルスレッドだと線形リストでいけそうだが、マルチスレッドを実現している処理系ではツリーである必要がある。

[Scala][Java][Lisp][Ruby]プログラミング言語Scala

Scalaというプログラミング言語を調べてみたので、レポート。

どんな言語か?

手続き型オブジェクト指向と関数型のハイブリッド言語。
静的型だが『型推論』をするため、面倒くさい変数の型宣言をある程度省略可能。
LispRubyのようにifやfor、ブロック等も式として扱われる。
クロージャ完備。関数の部分適用(cut)や合成(compose)ができる。
パターンマッチ(destructureing-bindみたいなもの?)ができる。

http://www.scala-lang.org/
http://ja.wikipedia.org/wiki/Scala

JavaVM上で動くインタプリタと、Javaバイトコードに変換するコンパイラの両方がある。
Javaライブラリとの親和性が高い。

性能

RubyPython、(未チューニングの)Lispよりも格段に速い。Javaで書いたのとほぼ同じ性能が出る。

静的型言語なので、コンパイラの最適化が効きやすく、fib(40)は1秒程度。
RubyPython、declareしていないCommon Lispなどの動的型言語だと、30秒〜1分以上かかる。
静的型が速い理由は、コンパイル時に型が決まるため。コンパイル時に型が決まっていれば、変数アクセス、関数呼び出し、算術演算などがほぼマシン語1命令に落とせる。動的型言語だと、(ほぼO(1)オーダだとしても)実行時に探索やディスパッチをする必要がある。

生産性

JavaVMを使っているので、メモリ管理はGC任せ。

クロージャがあるという点で、コードの重複除去はRubyと同じくらいできる。
マクロ相当の機能はないため、Common Lispと同じにはならない。

イメージ的には、Rubyに静的型の面倒臭さを付け加えた感じ。

フールプルーフ

文法要素のボリュームはJavaよりも大きいため、学習コストは比較的高い。従って、Javaすらわからん人に教えるのは難しい。

静的型であり、コンパイル時にある程度バグを弾いてくれるため、多少ユニットテストをサボる人がいても大丈夫。

結論

言語スペクトルの中でのScala
(抽象化・自由) Lisp <-> Ruby <-> Scala <-> Java (実行効率・束縛)
# ちなみに、左に行くほどスーパープログラマ向き、右に行くほどIT土方向きとなる。

groovyは完全にRubyPythonとかぶっていたが、Scalaは中間を行っているので存在意義はある。

JDK7でクロージャが追加されるかもしれないという流れの中では、Javaの将来の姿のようにも見える。

[Common Lisp][Java]ABCLで日本語を使う方法

ABCL(Armed Bear Common Lisp)で日本語を使うためのメモ。
(UTF-8限定)

ABCLのコンパイル

Readerが使っているストリームをUTF-8対応にする。

  • src/org/armedbear/lisp/Stream.java文字コード(InputStreamReaderをnewしている部分)をUTF-8に書き換える。(2箇所)
  • 普通にコンパイルしてJARファイルを作る。

SLIMEの設定

ABCL用のファイルの中で、utf-8-unixを通すようにする。

  • swank-abcl.lispの最後に、下記のコードを追加する。
(defimplementation find-external-format (coding-system)
  (if (equal coding-system "utf-8-unix")
    :default
    nil))
  • .emacsに下記の設定を追加する。
(setq slime-net-coding-system 'utf-8-unix)