2006/01/13 (金)
■[Scheme][Java][KAWA]SchemeからJavaの機能を呼び出す
The Kawa language frameworkを使うと、SchemeからJavaの機能を呼び出すことができる。
(事例1) Kawaで提供されていない機能をJavaの標準ライブラリを使って実装する
Kawaはあまりライブラリが充実しているとは言えない。その代表的なものが正規表現によるマッチングだ。幸いJavaにはjava.util.regexというパッケージが標準で付属しているので、これを使って正規表現によるマッチングを行うことができる。
java.util.regex.Patternに載っている例を移植してみよう。
- Java版:
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
- Scheme版:
(define-namespace Pattern <java.util.regex.Pattern>)
(define-namespace Matcher <java.util.regex.Matcher>)
(let* ((p (Pattern:compile "a*b"))
(m (Pattern:matcher p "aaaaab")))
(Matcher:matches m))
このようにほとんど機械的な置き換えで済む。Matcher.group()等も問題なく使える。ただし、結果がString型で返ってくる場合、そのままではSchemeの文字列として扱えないので、
(make <string> (Matcher:group m 2))
のようにする必要があった。
(事例2) Java用の3rd PartyライブラリをSchemeから利用する
各種スクリプト言語 (perl, ruby, python, Java) から, MeCab が提供する形態素解析の機能を利用可能です. 各バインディングは SWIG というプログラムを用いて, 自動生成されています.
スクリプト言語のバインディング
ということで、これをSchemeからJava経由で使えるようにする。
(require 'list-lib)
(define-namespace Node <jp.ac.aist_nara.cl.mecab.Node>)
(define-namespace Tagger <jp.ac.aist_nara.cl.mecab.Tagger>)
(java.lang.System:loadLibrary "MeCab")
(define mecab-tagger #f)
(define (mecab-init-tagger)
(let ((args ((primitive-array-new <String>) 3))
(setter (primitive-array-set <String>)))
(setter args 0 "java")
(setter args 1 "-r")
(setter args 2 "mecabrc")
(set! mecab-tagger (Tagger:new args))))
(define (mecab-parse str)
(or mecab-tagger (mecab-init-tagger))
(let ((node (Tagger:parseToNode mecab-tagger (as <String> str))))
(let loop ((node (Node:next node)) (r ()))
(if (Node:hasNode (Node:next node))
(loop (Node:next node)
(cons node r))
(reverse! r)))))
(mecab-parse "太郎は二郎にこの本を渡した.") ==> (太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー は 助詞,係助詞,*,*,*,*,は, ハ,ワ 二郎 名詞,固有名詞,一般,*,*,*,二郎,ニロウ,ニロー に 助詞,格助詞,一般,*,*,*, に,ニ,ニ この 連体詞,*,*,*,*,*,この,コノ,コノ 本 名詞,一般,*,*,*,*,本,ホン,ホン を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ 渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ た 助動詞,*,*,*,特 殊・タ,基本形,た,タ,タ . 未知語,*,*,*,*,*,*,*,*)
mecabrcを指定しているのは、Javaから呼び出す場合UTF-8版の辞書を使う必要があるため(SWIGの問題?)。mecabrcの内容は実質的に次の1行だけ。
dicdir = /usr/lib/mecab/dic/ipadic-utf8
さらに、このままだと扱いづらいので、以下のような関数を定義してSchemeの構造体として扱えるようにしている。
(define (node->word node)
(define (normalize str)
(if (string=? str "*")
#f
(string->symbol str)))
(let ((surface (make <string> (Node:getSurface node)))
(feature (*:split (as <String> (Node:getFeature node)) ",")))
(define (getter n)
(make <string> ((primitive-array-get <String>) feature n)))
(let ((pos (remove not
(map (lambda (n)
(normalize (getter n)))
'(0 1 2 3))))
(form (normalize (getter 4)))
(type (normalize (getter 5)))
(base (normalize (getter 6)))
(ruby (getter 7))
(reading (getter 8)))
(make-word surface pos form type base ruby reading))))
(define-record-type word
(make-word surface pos form type base ruby reading)
word?
(surface word-get-surface word-set-surface)
(pos word-get-pos word-set-pos)
(form word-get-form word-set-form)
(type word-get-type word-set-type)
(base word-get-base word-set-base)
(ruby word-get-ruby word-set-ruby)
(reading word-get-reading word-set-reading))
(事例3) SchemeからJavaの内部情報を弄る
リフレクション機能を使って遊んでみる。これを使ってコード補完とかも(頑張れば)できるかも。
(define-namespace Class <java.lang.Class>)
(define-namespace Field <java.lang.reflect.Field>)
(define-namespace Method <java.lang.reflect.Method>)
(define-namespace Pattern <java.util.regex.Pattern>)
(define-namespace Matcher <java.util.regex.Matcher>)
(define-namespace ClassType <gnu.bytecode.ClassType>)
(define (primitive-array->list element-type array)
(let ((len ((primitive-array-length element-type) array)))
(let loop ((i 0) (r '()))
(if (< i len)
(loop (+ i 1) (cons ((primitive-array-get element-type) array i) r))
(reverse r)))))
(define (declared-methods class :: <java.lang.Class>)
(primitive-array->list <java.lang.reflect.Method>
(Class:getDeclaredMethods class)))
(define (declared-fields class :: <java.lang.Class>)
(primitive-array->list <java.lang.reflect.Field>
(Class:getDeclaredFields class)))
(define (element-type->class element-type :: <gnu.bytecode.ClassType>)
(*:getReflectClass element-type))
(define (suggest-class-identifier class regex)
(define (writer obj)
(display " ")
(display obj)
(display "\n"))
(define (display-fields fields)
(display "Fields: ")
(display (length fields))
(display " field(s) found.\n")
(for-each writer fields))
(define (display-methods methods)
(display "Methods: ")
(display (length methods))
(display " method(s) found.\n")
(for-each writer methods))
(let* ((p (Pattern:compile regex (Pattern:.CASE_INSENSITIVE)))
(matcher (lambda (str :: <String>)
(Matcher:find (Pattern:matcher p str))))
(fields (filter (lambda (field)
(matcher (Field:getName field)))
(declared-fields class)))
(methods (filter (lambda (method)
(matcher (Method:getName method)))
(declared-methods class))))
(display-fields fields)
(display-methods methods)))
(suggest-class-identifier (element-type->class <String>) "^to") ==> Fields: 0 field(s) found. Methods: 6 method(s) found. public java.lang.String java.lang.String.toString() public char[] java.lang.String.toCharArray() public java.lang.String java.lang.String.toLowerCase(java.util.Locale) public java.lang.String java.lang.String.toLowerCase() public java.lang.String java.lang.String.toUpperCase(java.util.Locale) public java.lang.String java.lang.String.toUpperCase() (suggest-class-identifier (element-type->class <String>) "value") ==> Fields: 1 field(s) found. private final char[] java.lang.String.value Methods: 11 method(s) found. public static java.lang.String java.lang.String.copyValueOf(char[],int,int) public static java.lang.String java.lang.String.copyValueOf(char[]) public static java.lang.String java.lang.String.valueOf(java.lang.Object) public static java.lang.String java.lang.String.valueOf(char[]) public static java.lang.String java.lang.String.valueOf(char[],int,int) public static java.lang.String java.lang.String.valueOf(boolean) public static java.lang.String java.lang.String.valueOf(char) public static java.lang.String java.lang.String.valueOf(int) public static java.lang.String java.lang.String.valueOf(long) public static java.lang.String java.lang.String.valueOf(float) public static java.lang.String java.lang.String.valueOf(double)
プログラムを書いていると、どうしても実行して実験してみないと分からないことも多い。そんな時、Kawaを一種のJavaインタプリタとして利用することができる。
通常のJavaプログラミングでは、ソースコード修正→コンパイル→実行という手順を踏まないと行けないのに対して、Scheme からは入力した式をOn the flyにコンパイルして実行することができるから、対話的にちょっとした実験をするのに便利。究極のRADツール?
まとめ
KawaはSchemeインタプリタ/コンパイラを含むJavaで実装された多言語フレームワークである。Kawaで実行されるSchemeプログラムからは任意のJavaクラスを利用できる。具体的には次の3つの利用方法が考えられる:
最近はPerlやRubyのようなLightweight Languageが流行っているようなので、Kawa + Javaをその一つとして位置づけることもできるかもしれない。
参考URL
KawaでJavaのメソッドやフィールドに直接アクセスする方法など。
クラス構造のドキュメント。