KawaはJavaで実装したSchemeです。
Schemeでは部分文字列を取って大文字に変換する処理は以下のように書けます。
> (string-upcase (substring "abcdefg" 1 4))
BCD
Kawaは文字列としてjava.lang.String
をそのまま使用しているのでJavaのメソッドを使って以下のように書くこともできます。
> (invoke (invoke "abcdefg" 'substring 1 4) 'toUpperCase)
BCD
これはJavaでこう書いたのと同じです。
"abcdefg".substring(1, 4).toUpperCase()
一見Javaのメソッド呼び出しによく似ていますが、後ろにメソッド呼び出しを追加するたびに先頭に(invoke
を追加しないといけないのでストレスが溜まります。
Kawaにはもう一つのメソッド呼び出しの方法があります。
> (("abcdefg":substring 1 4):toUpperCase)
BCD
少し簡単になりましたが前述の例と同様に先頭に(
を追加する必要があるので煩雑です。
しかしSchemeなのでマクロを使って新しい文法を定義することができます。
(define-syntax ->
(syntax-rules ()
((_ o)
o)
((_ o (m a ...) r ...)
(-> (invoke o 'm a ...) r ...))
((_ o f r ...)
(-> (field o 'f) r ...))))
3番目のルールはオブジェクトのフィールドを参照するためのものです。
これで次のように書けるようになります。
> (-> "abcdefg" (substring 1 4) (toUpperCase))
BCD
Java8のストリームAPIを使うコードは以下のように書けます。
> (-> #(1 2 3 4 5)
(stream)
(filter (lambda (x) (< x 4)))
(map (lambda (x) (* x x)))
(toArray) )
[1 4 9]
ここに現れるstream
, filter
, map
, toArray
はすべてJavaのメソッドです。
filter
の引数は単なるラムダ式ですが、このコードが正しく動作するためにはPredicate
インタフェースを実装している必要があります。
Kawaにおけるラムダ式の実体はgnu.expr.ModuleMethod
クラスです。
> ((lambda x x):getClass)
class gnu.expr.ModuleMethod
このクラスはPredicate
もFunction
も実装していません。
ということはKawaはリフレクションでfilter
メソッドの引数がPredicate
インターフェースであることを調べて、ラムダ式をそのインスタンスに変換しているということになります。Javaのコンパイラと同じように型推論しているものと考えられます。
コンパイルしてラムダ式の部分を逆コンパイルすると以下のようになっています。
public class call$0 implements java.util.function.Predicate {
public boolean test(java.lang.Object);
Code:
0: aload_1
1: getstatic #10 // Field call.Lit3:Lgnu/math/IntNum;
4: invokestatic #16 // Method gnu/kawa/functions/NumberCompare.$Ls:(Ljava/lang/Object;Ljava/lang/Object;)Z
7: ireturn
public call$0();
Code:
0: aload_0
1: invokespecial #20 // Method java/lang/Object."<init>":()V
4: return
}
確かにPredicate
インタフェースを実装しているのがわかります。
この点に関してKawaはClojureよりもよくできていると思います。