Posted at

Kawaの型推論

More than 3 years have passed since last update.

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

このクラスはPredicateFunctionも実装していません。

ということは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よりもよくできていると思います。