[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() } }
Javaに対するScalaのメリット
Javaから他の言語に行きたいという悩み
Scalaの解決策
Scalaの便利なとこ(その1):getter/setterのうんざりを解消する
自分でJavaのプログラムを書いていて一番いらいらするのはgetter/setterの定義。
冗長で見るに耐えない。
Sunが気を利かせて、ライン数の水増し手法を提供してくれたのか?(笑)
ScalaのJavaに対するメリットの中では一番くだらないんじゃないかとも思いますが、意外と重要でしかもわかりやすいメリットだと思います。
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 LispやRubyだと、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というプログラミング言語を調べてみたので、レポート。
どんな言語か?
手続き型オブジェクト指向と関数型のハイブリッド言語。
静的型だが『型推論』をするため、面倒くさい変数の型宣言をある程度省略可能。
LispやRubyのようにifやfor、ブロック等も式として扱われる。
クロージャ完備。関数の部分適用(cut)や合成(compose)ができる。
パターンマッチ(destructureing-bindみたいなもの?)ができる。
http://www.scala-lang.org/
http://ja.wikipedia.org/wiki/Scala
JavaVM上で動くインタプリタと、Javaのバイトコードに変換するコンパイラの両方がある。
Javaライブラリとの親和性が高い。
性能
RubyやPython、(未チューニングの)Lispよりも格段に速い。Javaで書いたのとほぼ同じ性能が出る。
静的型言語なので、コンパイラの最適化が効きやすく、fib(40)は1秒程度。
RubyやPython、declareしていないCommon Lispなどの動的型言語だと、30秒〜1分以上かかる。
静的型が速い理由は、コンパイル時に型が決まるため。コンパイル時に型が決まっていれば、変数アクセス、関数呼び出し、算術演算などがほぼマシン語1命令に落とせる。動的型言語だと、(ほぼO(1)オーダだとしても)実行時に探索やディスパッチをする必要がある。
生産性
JavaVMを使っているので、メモリ管理はGC任せ。
クロージャがあるという点で、コードの重複除去はRubyと同じくらいできる。
マクロ相当の機能はないため、Common Lispと同じにはならない。
イメージ的には、Rubyに静的型の面倒臭さを付け加えた感じ。
[Common Lisp][Java]ABCLで日本語を使う方法
ABCL(Armed Bear Common Lisp)で日本語を使うためのメモ。
(UTF-8限定)
ABCLのコンパイル
Readerが使っているストリームをUTF-8対応にする。