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をその一つとして位置づけることもできるかもしれない。