Julia でポアソン回帰 - glm

以前、「R でポアソン回帰 - glm, MCMCpack」 にて試した GLM によるポアソン回帰を Julia で実施してみました。

なお、Julia は開発中の v0.4.0 を使用しました。

サンプルソースhttp://github.com/fits/try_samples/tree/master/blog/20150223/

はじめに

今回は下記のパッケージを使用しますので、予めインストールしておきます。

パッケージ名 用途
DataFrames csv ファイルの読み込み。データフレーム化
GLM GLM の実施
Gadfly グラフの描画
Cairo PNGファイルへのグラフ描画
パッケージのインストール例
> julia
・・・

julia> Pkg.add("DataFrames")
・・・
julia> Pkg.add("GLM")
・・・
julia> Pkg.add("Gadfly")
・・・
julia> Pkg.add("Cairo")
・・・

ポアソン回帰を試すデータは 以前 に使ったものをそのまま使用します。

データ data3a.csv
y,x,f
6,8.31,C
6,9.44,C
6,9.5,C
12,9.07,C
10,10.16,C
・・・

データ内容は下記の通りです。

今回は、体サイズ x が種子数 y にどのように影響しているかをポアソン回帰で解析します。

項目 内容
y 種子数
x 植物の体サイズ
f 施肥処理

GLM によるポアソン回帰

y ~ x のモデルを使ってポアソン回帰を実施します。

以前、R で実施したものと同じポアソン回帰を行うには、glm 関数へ Poisson()LogLink() (リンク関数)を指定します。

poissonGlm.jl
using DataFrames, GLM

d = readtable("data3a.csv")

res = glm(y ~ x, d, Poisson(), LogLink())

println(res)

実行結果は、以下のように R の結果とほぼ同じになりました。 (前回 の結果を参照)

今回は Julia の開発バージョンを使ったため deprecated syntax の WARNING がいくつか出力されました。

実行結果
> julia poissonGlm.jl
・・・
Coefficients:
              Estimate Std.Error z value Pr(>|z|)
(Intercept)    1.29172  0.363686 3.55174   0.0004
x            0.0756619 0.0356042 2.12509   0.0336

この結果より、平均種子数の予測値を求める関数は { \lambda = \exp(1.29172 + 0.0756619 x) } となります。

予測線の描画1

次に、ポアソン回帰の結果を使って種子数 y の予測線を描画し PNG ファイルへ出力してみます。

まず、種子数 y の予測値を算出するために、data3a.csv における x の最小値 7.19 から最大値 12.4 までを 0.1 刻みにした配列 xx を作成しました。

coef 関数を使えば glm の結果から Coefficients の Estimate 値を配列として取得できるので、これと xx を使って種子数 y の予測値を算出できます。

plotlayer を組み合わせる事で複数のグラフを重ねて描画できます。

poissonGlm_draw1.jl
using DataFrames, GLM, Gadfly

d = readtable("data3a.csv")

res = glm(y ~ x, d, Poisson(), LogLink())

# x の最小値 7.19 ~ 最大値 12.4 まで 0.1 刻みのデータを用意
xx = [minimum(d[:x]):0.1:maximum(d[:x])]
# 種子数 y の予測値を算出
yy = exp(coef(res)[1] + coef(res)[2] * xx)

# グラフの描画
p = plot(
    # data3a.csv の内容を描画
    layer(d, x = "x", y = "y", color = "f", Geom.point),
    # 予測線の描画
    layer(DataFrame(x = xx, y = yy), x = "x", y = "y", Geom.line)
)

# PNG 形式で出力
draw(PNG("poissonGlm_draw1.png", 500px, 400px), p)

f:id:fits:20150223203652p:plain

ここで、xx は以下のような内容です。

xx の内容
53-element Array{Float64,1}:
  7.19
  7.29
  ・・・
 12.29
 12.39

予測線の描画2

最後に、種子数 y の予測値を predict 関数で算出し、同様のグラフを描画します。

predict を使うには x の値だけでは無く (Intercept) の Estimate 値へ乗算する値も必要になるので、下記では DataFrame (下記の nd) へ項目 n (常に 1) として用意しました。

predict 内では { \exp(1.29172 n + 0.0756619 x) } のような計算が実施されます。

poissonGlm_draw2.jl
・・・
res = glm(y ~ x, d, Poisson(), LogLink())

# 7.19 ~ 12.4 まで 0.1 刻みのデータを用意
xx = [minimum(d[:x]):0.1:maximum(d[:x])]
# predict へ渡すデータを作成
nd = DataFrame(n = [1 for i = 1:length(xx)], x = xx)

# predict で種子数 y の予測値を算出
yy = predict(res, nd)

p = plot(
    # data3a.csv の内容を描画
    layer(d, x = "x", y = "y", color = "f", Geom.point),
    # 予測線の描画
    layer(DataFrame(x = xx, y = yy), x = "x", y = "y", Geom.line)
)

draw(PNG("poissonGlm_draw2.png", 500px, 400px), p)

f:id:fits:20150223203711p:plain

Compiler Tree API で Java ソースファイルをパースする2 - Groovy で実装

前回 の処理を Groovy で実装してみました。

  • Java SE 8u31
  • Groovy 2.4.0

今回使用したソースは http://github.com/fits/try_samples/tree/master/blog/20150216/

(a) com.sun.tools.javac.main.JavaCompiler 利用

前回 Java で実装した内容を Groovy で実装し直しただけです。

javacompiler_parse.groovy
import javax.tools.ToolProvider

import com.sun.tools.javac.main.JavaCompiler
import com.sun.tools.javac.util.Context

def fileManager = ToolProvider.systemJavaCompiler.getStandardFileManager(null, null, null)

def compiler = new JavaCompiler(new Context())

fileManager.getJavaFileObjects(args).each {
    def cu = compiler.parse(it)
    cu.accept(new SampleVisitor(), null)
}

groovy コマンドで実行するだけです。 当然ながら前回の Java 版と同じ結果になります。

実行結果
> groovy javacompiler_parse.groovy Sample.java

class: Sample
method: sample1
method: sample2
lambda: (n)->n * 2
class: SampleChild
method: child1
lambda: (n)->{
    System.out.println(n);
    return n > 0 && n % 2 == 0;
}
method: child2

(b) com.sun.source.util.JavacTask 利用

こちらも Groovy で実装し直しただけです。

javactask_parse.groovy
import javax.tools.ToolProvider

def compiler = ToolProvider.systemJavaCompiler
def fileManager = compiler.getStandardFileManager(null, null, null)

def task = compiler.getTask(null, fileManager, null, null, null, fileManager.getJavaFileObjects(args))

task.parse().each {
    it.accept(new SampleVisitor(), null)
}

ただし、こちらは groovy コマンドでスクリプト実行すると MissingMethodException が発生しました。

実行結果1
> groovy javactask_parse.groovy Sample.java

Caught: groovy.lang.MissingMethodException: No signature of method: com.sun.tools.javac.tree.JCTree$JCCompilationUnit.accept() is applicable for argument types: (SampleVisitor, null) values: [SampleVisitor@2cf3d63b, null]
Possible solutions: accept(com.sun.source.tree.TreeVisitor, java.lang.Object), 
accept(com.sun.tools.javac.tree.JCTree$Visitor), 
accept(com.sun.source.tree.TreeVisitor, java.lang.Object), 
accept(com.sun.tools.javac.tree.JCTree$Visitor), grep(), inspect()
・・・

JCCompilationUnit.accept(SampleVisitor, null) の箇所は javacompiler_parse.groovy でも同じだと思うのですが、こちらの方だけがエラーになる理由はよく分かりません。(Groovy の内部処理に何らかの原因があるのかもしれませんが)

ちなみに、groovyc でコンパイルして実行すると正常に実行できました。

実行結果2
> groovyc javactask_parse.groovy

> java -cp .;%JAVA_HOME%/lib/tools.jar;%GROOVY_HOME%/embeddable/groovy-all-2.4.0-indy.jar javactask_parse Sample.java

class: Sample
method: sample1
method: sample2
lambda: (n)->n * 2
class: SampleChild
method: child1
lambda: (n)->{
    System.out.println(n);
    return n > 0 && n % 2 == 0;
}
method: child2

Compiler Tree API で Java ソースファイルをパースする

javax.tools と Compiler Tree API を使って Java のソースファイルをパースしてみました。

前回 と同じように、ソースファイルを AST 化した CompilationUnitTree (実際は JCTree$JCCompilationUnit) を取得し、簡単な TreeVisitor を適用します。

今回のソースは http://github.com/fits/try_samples/tree/master/blog/20150208/

はじめに

ソースファイルを CompilationUnitTree 化する一般的な方法が分からなかったので、とりあえず以下のような方法で試してみました。 (com.sun.tools.javac.main.JavaCompiler は Compiler Tree API ではありませんが)

  • (a) com.sun.tools.javac.main.JavaCompiler を利用
  • (b) com.sun.source.util.JavacTask を利用

今回は、取得した CompilationUnitTree へ下記の TreeVisitor 実装クラスを適用します。

SampleVisitor.java
import com.sun.source.util.TreeScanner;
import com.sun.source.tree.*;

public class SampleVisitor extends TreeScanner<Void, Void> {
    @Override
    public Void visitClass(ClassTree node, Void p) {
        // クラス名の出力
        System.out.println("class: " + node.getSimpleName());
        return super.visitClass(node, p);
    }

    @Override
    public Void visitMethod(MethodTree node, Void p) {
        // メソッド名の出力
        System.out.println("method: " + node.getName());
        return super.visitMethod(node, p);
    }

    @Override
    public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
        // ラムダ式の内容を出力
        System.out.println("lambda: " + node);
        return super.visitLambdaExpression(node, p);
    }
}

SampleVisitor は以下の内容を出力します。

(a) com.sun.tools.javac.main.JavaCompiler 利用

com.sun.tools.javac.main.JavaCompiler を使った処理内容は以下の通りです。

  • (1) javax.tools.ToolProvider から javax.tools.JavaCompiler 取得
  • (2) javax.tools.JavaCompiler から StandardJavaFileManager 取得
  • (3) StandardJavaFileManager から JavaFileObject (の Iterable) 取得
  • (4) JavaFileObject を com.sun.tools.javac.main.JavaCompiler で parse して CompilationUnitTree (JCTree$JCCompilationUnit) を取得
  • (5) CompilationUnitTree へ TreeVisitor を適用

javax.tools.JavaCompilercom.sun.tools.javac.main.JavaCompiler は別物なのでご注意ください。

StandardJavaFileManagergetJavaFileObjects メソッドはソースファイル名を可変長引数の文字列で与えれば JavaFileObject (の Iterable) を取得できるので、コマンドライン引数の args をそのまま渡しています。

JavaCompilerParse.java
import java.io.IOException;

import javax.tools.ToolProvider;
import javax.tools.StandardJavaFileManager;

import com.sun.source.tree.CompilationUnitTree;

import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;

public class JavaCompilerParse {
    public static void main(String... args) throws IOException {
        try (
            // (1) (2)
            StandardJavaFileManager fileManager = 
                ToolProvider.getSystemJavaCompiler()
                    .getStandardFileManager(null, null, null)
        ) {
            JavaCompiler compiler = new JavaCompiler(new Context());

            // (3)
            fileManager.getJavaFileObjects(args).forEach(f -> {
                // (4) AST 取得
                CompilationUnitTree cu = compiler.parse(f);
                // (5) クラス・メソッド名とラムダ式の出力
                cu.accept(new SampleVisitor(), null);
            });
        }
    }
}

JDK の lib/tools.jar を CLASSPATH へ指定してコンパイルする必要があります。

コンパイル
> javac -cp %JAVA_HOME%/lib/tools.jar *.java

実行する際も lib/tools.jar が必要です。
今回は適当に用意した Sample.java をパースしてみました。

実行結果
> java -cp .;%JAVA_HOME%/lib/tools.jar JavaCompilerParse Sample.java

class: Sample
method: sample1
method: sample2
lambda: (n)->n * 2
class: SampleChild
method: child1
lambda: (n)->{
    System.out.println(n);
    return n > 0 && n % 2 == 0;
}
method: child2

使用した Sample.java の内容は以下の通りです。

Sample.java
import java.util.stream.IntStream;

class Sample {
    public String sample1() {
        return "sample";
    }

    public int sample2(int x) {
        return IntStream.range(0, x).map(n -> n * 2).sum();
    }

    class SampleChild {
        public void child1(int... nums) {
            IntStream.of(nums).filter(n -> {
                System.out.println(n);
                return n > 0 && n % 2 == 0;
            }).forEach(System.out::println);
        }

        private void child2() {
        }
    }
}

(b) com.sun.source.util.JavacTask 利用

com.sun.source.util.JavacTask には、CompilationUnitTree (の Iterable) を取得する parse メソッドが用意されていますが、JavacTask を取得する一般的な方法が分からなかったので、今回は javax.tools.JavaCompilergetTask メソッドで取得した JavaCompiler.CompilationTask の実装オブジェクト (com.sun.tools.javac.api.JavacTaskImpl) を JavacTask へキャストしました。

  • (1) javax.tools.ToolProvider から javax.tools.JavaCompiler 取得
  • (2) javax.tools.JavaCompiler から StandardJavaFileManager 取得
  • (3) javax.tools.JavaCompiler を使って StandardJavaFileManager と JavaFileObject (の Iterable) から JavacTask 取得
  • (4) JavacTask を parse して CompilationUnitTree (の Iterable) を取得
  • (5) CompilationUnitTree へ TreeVisitor を適用
JavacTaskParse.java
import java.io.IOException;

import javax.tools.ToolProvider;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;

import com.sun.source.util.JavacTask;

public class JavacTaskParse {
    public static void main(String... args) throws IOException {
        // (1)
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        try (
            // (2)
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)
        ) {
            // (3)
            JavacTask task = (JavacTask)compiler.getTask(null, fileManager, null, null, null, fileManager.getJavaFileObjects(args));
            // (4) (5)
            task.parse().forEach(cu -> cu.accept(new SampleVisitor(), null));
        }
    }
}
実行結果
> java -cp .;%JAVA_HOME%/lib/tools.jar JavacTaskParse Sample.java

class: Sample
method: sample1
method: sample2
lambda: (n)->n * 2
class: SampleChild
method: child1
lambda: (n)->{
    System.out.println(n);
    return n > 0 && n % 2 == 0;
}
method: child2

アノテーションプロセッサで AST 変換 - Lombok を参考にして変数の型をコンパイル時に変更

Java のボイラープレートを補完してくれる Lombok の処理内容が興味深かったので、これを真似た簡単なサンプルプログラムを作ってみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20150117/

はじめに

Lombok はアノテーションプロセッサを使って AST (抽象構文木) の変換を実施しています。

Lombok の使い方

まずは Lombok を使って下記のような Java ソースのコンパイルを試してみます。 val@Value が Lombok の機能を使用している箇所です。

Sample.java
import lombok.val;
import lombok.Value;

public class Sample {
    public static void main(String... args) {
        // lombok.val の使用
        val d = new Data("sample1", 10);
        System.out.println(d);
    }

    // lombok.Value の使用
    @Value
    private static class Data {
        private String name;
        private int value;
    }
}

Service Provider 機能(META-INF/services)を使用するため、javac 時に classpath へ lombok.jar を指定するだけで適用されます。

javac によるコンパイル (Lombok 使用)
> javac -cp lombok.jar Sample.java

CFR を使って Sample.class の内容を確認してみると、lombok.vallombok.Value が消え、代わりに型やメソッドを補完している事が分かります。

Sample.class の内容確認 (CFR 利用)
> java -jar cfr_0_94.jar Sample.class

/*
 * Decompiled with CFR 0_94.
 */
import java.beans.ConstructorProperties;
import java.io.PrintStream;

public class Sample {
    public static /* varargs */ void main(String ... arrstring) {
        Data data = new Data("sample1", 10);
        System.out.println(data);
    }

    private static final class Data {
        private final String name;
        private final int value;

        @ConstructorProperties(value={"name", "value"})
        public Data(String string, int n) {
            this.name = string;
            this.value = n;
        }

        public String getName() {
            return this.name;
        }

        public int getValue() {
            return this.value;
        }

        public boolean equals(Object object) {
            if (object == this) {
                return true;
            }
            if (!(object instanceof Data)) {
                return false;
            }
            Data data = (Data)object;
            String string = this.getName();
            String string2 = data.getName();
            if (string == null ? string2 != null : !string.equals(string2)) {
                return false;
            }
            if (this.getValue() != data.getValue()) {
                return false;
            }
            return true;
        }

        public int hashCode() {
            int n = 1;
            String string = this.getName();
            n = n * 59 + (string == null ? 0 : string.hashCode());
            n = n * 59 + this.getValue();
            return n;
        }

        public String toString() {
            return "Sample.Data(name=" + this.getName() + ", value=" + this.getValue() + ")";
        }
    }

}

Lombok の仕組み

次に、Lombok の仕組みを簡単に説明します。

Lombok はアノテーションプロセッサ内 (lombok.javac.apt.Processor) にて RoundEnvironment を元に AST を取得し変換します。

javac 実行時の処理を大雑把に書くと下記のようになっています。

  • (1) lombok.core.AnnotationProcessor を処理
  • (2) lombok.javac.apt.Processor を処理
  • (3) lombok.javac.JavacTransformer を処理
  • (4) AnnotationVisitor を処理
  • (5) 各種 AST 変換用のハンドラ (lombok.javac.handlers パッケージ内のクラス) を処理

lombok.jar の META-INF/services/javax.annotation.processing.Processor に (1) のクラス名が記載されているため、アノテーションプロセッサの仕組みによって (1) が実行されます。

(5) の各種ハンドラは lombok.javac.HandlerLibrary が以下のファイルから取得し管理します。

  • META-INF/services/lombok.javac.JavacASTVisitor (visitorHandlers)
  • META-INF/services/lombok.javac.JavacAnnotationHandler (annotationHandlers)

JavacASTVisitor インターフェース実装クラスの lombok.javac.handlers.HandleVal (現時点では唯一の JavacASTVisitor 実装ハンドラ) は、lombok.val を型として使っている変数を適切な型に変更するという処理を行います。

HandleVal の処理内容が興味深かったので、今回はこれを真似た簡易的な処理を作ります。

アノテーションプロセッサで AST 変換

ここからは、Lombok における val の処理を真似たアノテーションプロセッサを自作していきます。

(1) AST の取得

まずは AST を取得して出力するだけのアノテーションプロセッサを作ります。

Compiler Tree API を使用するので、Gradle でビルドする場合は JDK の tools.jar を dependencies へ設定しておきます。

build.gradle
apply plugin: 'java'

dependencies {
    compile files("${System.properties['java.home']}/../lib/tools.jar")
}

次に、Service Provider 設定ファイルを用意しておきます。

このファイルを用意しておけば、JAR ファイルを classpath へ指定するだけで sample.SampleProcessor1 が実行されます。

src/main/resources/META-INF/services/javax.annotation.processing.Processor
sample.SampleProcessor1

AbstractProcessor を extends してアノテーションプロセッサを作成します。

@SupportedAnnotationTypes("*") アノテーションを付与する事で、アノテーションの使用有無に関わらずコンパイル対象の全ソースを RoundEnvironment から取得できるようになります。

Lombok のソースでは JCCompilationUnit へキャストして使っていますが、CompilationUnitTree が AST に該当します。

CompilationUnitTree を取得するため Trees を使っています。

src/main/sample/SampleProcessor1.java
package sample;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.annotation.processing.SupportedAnnotationTypes;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import com.sun.source.tree.CompilationUnitTree;

import com.sun.source.util.Trees;
import com.sun.source.util.TreePath;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*") //コンパイル対象の全ソースを対象とする
public class SampleProcessor1 extends AbstractProcessor {
    private Trees trees;

    @Override
    public void init(ProcessingEnvironment procEnv) {
        trees = Trees.instance(procEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // コンパイル対象の全ソースを処理
        roundEnv.getRootElements().stream().map(this::toUnit).forEach(u -> {
            System.out.println("----- CompilationUnitTree -----");
            // AST の内容を出力
            System.out.println(u);
        });

        return false;
    }

    // AST の取得
    private CompilationUnitTree toUnit(Element el) {
        TreePath path = trees.getPath(el);
        return path.getCompilationUnit();
    }
}

今回は Java 8 の API を使ったので @SupportedSourceVersion(SourceVersion.RELEASE_8) を付けています。

ビルド

> gradle build

:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

動作確認 (アノテーションプロセッサの適用)

ビルド結果の build/libs/anp-sample1.jar ファイルを以下の Java ソース (A1.java と A2.java) のコンパイルに使ってみます。

A1.java
public class A1 {
    public void sample() {
        int i = 10;
    }
}

下記の var は後で使うので今のところは気にしないで下さい。

A2.java
public class A2 {
    // 下記の var はインターフェースやクラスで定義しても同じ
    @interface var {}

    public var a = 10;
    public String b = "bbb";

    public void sample() {
        var msg = "test data";
        System.out.println(msg);
    }
}

Lombok と同様に javac 時に classpath へ anp-sample1.jar を指定するだけです。

実行結果 (javac 実行)
> javac -cp anp-sample1.jar *.java

----- CompilationUnitTree -----

public class A1 {

    public A1() {
        super();
    }

    public void sample() {
        int i = 10;
    }
}
----- CompilationUnitTree -----

public class A2 {

    public A2() {
        super();
    }

    @interface var {
    }
    public var a = 10;
    public String b = "bbb";

    public void sample() {
        var msg = "test data";
        System.out.println(msg);
    }
}
A2.java:5: エラー: 不適合な型: intをvarに変換できません:
        public var a = 10;
                       ^
A2.java:9: エラー: 不適合な型: Stringをvarに変換できません:
                var msg = "test data";
                          ^
エラー2個

CompilationUnitTree オブジェクトを println するとソースが出力されました。

ここで重要なのは、var を使った箇所のエラーが SampleProcessor1 処理の後に出力されている点です。 (var は int や String では無いので当然のエラーです)

つまり、型チェックはアノテーションプロセッサの後に実施される事が分かります。

であれば、アノテーションプロセッサ内で適切な型に変えてしまえばコンパイルを通す事が可能という事になり、Lombok の val は実際にこの仕組みを利用しています。

(2) AST 内の var 型を Object へ変更

次に、(1) でエラーが発生した A2.java 内の varjava.lang.Object へ変更するように実装します。

CompilationUnitTree (AST) は Visitor パターンで処理できるようになっています。

今回は var を使っている変数定義の部分を処理させたいだけなので、TreeScannervisitVariable メソッドをオーバーライドした内部クラス (VarVisitor) を用意しました。

JCVariableDeclvartype フィールドに変数の型が設定されており、これを変更すれば変数の型が変わります。

vartype が var という名前の型 (所属パッケージに関係なく) であれば java.lang.Object へ変更するようにしてみました。

src/main/sample/SampleProcessor2.java
package sample;
・・・
import com.sun.source.tree.VariableTree;
・・・
import com.sun.tools.javac.model.JavacElements;

import com.sun.tools.javac.processing.JavacProcessingEnvironment;

import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.JCTree.JCExpression;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class SampleProcessor2 extends AbstractProcessor {
    private Trees trees;
    private TreeMaker maker;
    private JavacElements elements;

    @Override
    public void init(ProcessingEnvironment procEnv) {
        trees = Trees.instance(procEnv);

        JavacProcessingEnvironment env = (JavacProcessingEnvironment)procEnv;

        maker = TreeMaker.instance(env.getContext());
        elements = JavacElements.instance(env.getContext());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // コンパイル対象の全ソースを処理
        roundEnv.getRootElements().stream().map(this::toUnit).forEach(this::processUnit);

        return false;
    }

    private CompilationUnitTree toUnit(Element el) {
        TreePath path = trees.getPath(el);
        return path.getCompilationUnit();
    }

    private void processUnit(CompilationUnitTree cu) {
        // Visitor パターンで AST を処理
        cu.accept(new VarVisitor(), null);
    }

    private class VarVisitor extends TreeScanner<Void, Void> {
        // 変数定義の処理
        @Override
        public Void visitVariable(VariableTree node, Void p) {
            System.out.println("visitVariable: " + node);

            if (node instanceof JCVariableDecl) {
                JCVariableDecl vd = (JCVariableDecl)node;

                if ("var".equals(vd.vartype.toString())) {
                    JCExpression ex = maker.Ident(elements.getName("java"));
                    ex = maker.Select(ex, elements.getName("lang"));
                    ex = maker.Select(ex, elements.getName("Object"));
                    // 型を java.lang.Object へ変更
                    vd.vartype = ex;
                }
            }
            return null;
        }
    }
}

java.lang.Object の JCExpression を構築している箇所はもっと上手い方法がありそうですが、とりあえず Lombok の処理を真似ました。

src/main/resources/META-INF/services/javax.annotation.processing.Processor
sample.SampleProcessor2

ビルド

> gradle build

・・・

動作確認 (アノテーションプロセッサの適用)

(1) の時と同様に A1.java と A2.javaコンパイルに使ってみます。

実行結果 (javac 実行)
> javac -cp anp-sample2.jar *.java

visitVariable: int i = 10
visitVariable: public var a = 10
visitVariable: public String b = "bbb"
visitVariable: var msg = "test data"

今回はコンパイルエラーが発生せず、A1.java と A2.java 内の変数定義を visitVariable で処理しています。

CFR で A2.class の内容を確認してみると、var を使っていた変数の型が、一応 java.lang.Object に変わっています。 ("test data" の直接の型は String となっていますが)

A2.class の内容確認 (CFR 利用)
> java -jar cfr_0_94.jar A2.class

/*
 * Decompiled with CFR 0_94.
 */
import java.io.PrintStream;

public class A2 {
    public Object a = 10;
    public String b = "bbb";

    public void sample() {
        String string = "test data";
        System.out.println((Object)string);
    }

    static @interface var {
    }

}

最後に、この仕組みを流用すれば Java で型に別名を付ける機能 (ソース内だけの型エイリアス・型シノニムのようなもの) を実現できるかもしれません。

Akka Streams で skip・take 処理

前回の 「Reactor で skip・take 処理」 と同様の処理を Akka Streams を使用し Java 8 で実装してみました。

  • Akka Streams 1.0 M2

ソースは http://github.com/fits/try_samples/tree/master/blog/20150112/

はじめに

Gradle を使ってビルド・実行するため、下記のような build.gradle を用意しました。

build.gradle
apply plugin: 'application'

repositories {
    jcenter()
}

dependencies {
    compile 'com.typesafe.akka:akka-stream-experimental_2.11:1.0-M2'
}

mainClassName = 'sample.Sample1'
//mainClassName = 'sample.Sample2'
//mainClassName = 'sample.PublisherSample1'
//mainClassName = 'sample.PublisherSample2'

Iterable を使った処理1

Iterable を使った固定的なデータストリームに対して skip・take 処理を実装してみます。

まず、下記 (2) のように ActorSystemFlowMaterializer オブジェクトを作成し、処理完了時に ActorSystem を shutdown する処理 (3) を用意しておきます。

今回は sample1 ~ sample6 のデータを Java 8 の Stream を使って作りましたが、Stream は Iterable インターフェースを持っておらず、Akka Streams には今のところ Source.from(Stream) のようなメソッドも用意されていないので、List へ変換するなどして Iterable 化する必要があります。 (4)

(5) では、(4) を使って Source を生成し、skip・take 処理を実施しています。 なお、Akka Streams では skip 処理を drop メソッドとして用意しています。

最後に、(1) は Akka の不要なログ出力 (info レベル) を抑制するための措置です。 (設定ファイルで設定する方法もあります)

akka.logleveloff にしてログ出力を無効化する事もできますが、そうするとエラーログすら出力されなくなるのでご注意下さい。

Sample1.java
package sample;

import akka.actor.ActorSystem;
import akka.dispatch.OnComplete;
import akka.stream.FlowMaterializer;
import akka.stream.javadsl.Source;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;

import scala.runtime.BoxedUnit;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Sample1 {
    public static void main(String... args) {
        // (1) Akka の不要なログ出力を抑制するための設定
        final Config config = ConfigFactory.load()
            .withValue("akka.loglevel", ConfigValueFactory.fromAnyRef("error"));

        // (2) ActorSystem・FlowMaterializer の作成
        final ActorSystem system = ActorSystem.create("sample", config);
        final FlowMaterializer materializer = FlowMaterializer.create(system);

        // (3) 終了時の処理
        final OnComplete<BoxedUnit> complete = new OnComplete<BoxedUnit>() {
            @Override
            public void onComplete(Throwable failure, BoxedUnit success) {
                system.shutdown();
            }
        };

        // (4) sample1 ~ sample6
        List<String> data = IntStream.range(1, 7).mapToObj(i -> "sample" + i).collect(Collectors.toList());
        // 以下でも可
        //Iterable<String> data = () -> IntStream.range(1, 7).mapToObj(i -> "sample" + i).iterator();

        // (5) 処理
        Source.from(data)
            .drop(3)
            .take(2)
            .map(s -> "#" + s)
            .foreach(System.out::println, materializer)
            .onComplete(complete, system.dispatcher());
    }
}

また、(4) (5) の箇所は下記のように実装する事もできます。

Stream<String> stream = IntStream.range(1, 7).mapToObj(i -> "sample" + i);

Source.from((Iterable<String>)stream::iterator)
    .drop(3)
    ・・・
実行結果
> gradle -q run

#sample4
#sample5

Iterable を使った処理2

Sample1.javaFlow を使って書き換えると下記のようになります。
Flow によってストリームの加工処理部分を分離できます。

Sample2.java
・・・
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Sink;
・・・

public class Sample2 {
    public static void main(String... args) {
        ・・・

        Flow<String, String> flow = Flow.<String>create()
            .drop(3)
            .take(2)
            .map(s -> "#" + s);

        List<String> data = IntStream.range(1, 7).mapToObj(i -> "sample" + i).collect(Collectors.toList());

        flow.runWith(
            Source.from(data), 
            Sink.foreach(System.out::println), 
            materializer
        ).onComplete(complete, system.dispatcher());
    }
}
実行結果
> gradle -q run

#sample4
#sample5
build.gradle
・・・
//mainClassName = 'sample.Sample1'
mainClassName = 'sample.Sample2'

Publisher を使った処理1

これまで固定的なデータを使って処理を行いましたが、前回Broadcaster のような処理 (ストリームへ任意の要素を送信) を Akka Streams で実現するには org.reactivestreams.Publisher を利用できます。

RxJava 等と組み合わせるのが簡単だと思いますが、今回は Publisher の実装クラスを自作してみました。(SamplePublisher クラス)

まずは sample1 ~ sample6 をそのまま出力する処理を実装してみましたが、2点ほど注意点がありました。

  • (1) akka.stream.materializer.initial-input-buffer-size のデフォルト値である 4 を超えたデータ数がバッファに格納されるとエラー (Input buffer overrun) が発生する
  • (2) Subscriber に対して onSubscribe しないと onNext が機能しない

(1) を回避するために buffer メソッドを使って十分なバッファ数を確保するように変更しました。

OverflowStrategy に関しては用途に応じて設定する事になると思いますが、dropXXX や error を使用すると、バッファが溢れた際にデータが欠ける事になるので注意が必要です。

ちなみに、(1) のデフォルト設定は akka-stream-experimental_2.11-1.0-M2.jar 内の reference.conf ファイルで設定されています。

また、(2) の対策のため、Subscriber (実体は ActorProcessor) へ何も処理を行わない Subscription を onSubscribe しています。

PublisherSample1.java
・・・
import akka.stream.OverflowStrategy;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
・・・

public class PublisherSample1 {
    public static void main(String... args) {
        ・・・

        try (SamplePublisher<String> publisher = new SamplePublisher<>()) {

            Source.from(publisher)
                // (1) Input buffer overrun の防止策
                .buffer(10, OverflowStrategy.backpressure())
                .map(s -> "#" + s)
                .foreach(System.out::println, materializer)
                .onComplete(complete, system.dispatcher());

            IntStream.range(1, 7)
                .mapToObj(i -> "sample" + i)
                .forEach(d -> publisher.send(d));
        }
    }

    private static class SamplePublisher<T> implements Publisher<T>, AutoCloseable {
        private List<Subscriber<? super T>> list = new ArrayList<>();

        @Override
        public void subscribe(Subscriber<? super T> s) {
            list.add(s);
            // (2) 下記を実施しないと onNext が機能しない
            s.onSubscribe(new Subscription() {
                @Override public void request(long n) {}
                @Override public void cancel() {}
            });
        }

        @Override
        public void close() {
            list.forEach(Subscriber::onComplete);
        }

        public void send(T msg) {
            // Subscriber へデータ送信
            list.forEach(s -> s.onNext(msg));
        }
    }
}

SamplePublisher クラス内で Subscriber を List にて管理していますが、上記処理では subscribe は 1度しか実行されないので List で管理する必要は特にありません。

実行結果
> gradle -q run

#sample1
#sample2
#sample3
#sample4
#sample5
#sample6
build.gradle
・・・
//mainClassName = 'sample.Sample2'
mainClassName = 'sample.PublisherSample1'

Publisher を使った処理2

最後に skip(drop)・take の処理を追加してみます。

PublisherSample2.java
・・・

public class PublisherSample2 {
    public static void main(String... args) {
        ・・・

        try (SamplePublisher<String> publisher = new SamplePublisher<>()) {

            Source.from(publisher)
                // Input buffer overrun の防止策
                .buffer(10, OverflowStrategy.backpressure())
                .drop(3)
                .take(2)
                .map(s -> "#" + s)
                .foreach(System.out::println, materializer)
                .onComplete(complete, system.dispatcher());

            IntStream.range(1, 7)
                .mapToObj(i -> "sample" + i)
                .forEach(d -> publisher.send(d));
        }
    }

    private static class SamplePublisher<T> implements Publisher<T>, AutoCloseable {
        ・・・
    }
}
実行結果
> gradle -q run

#sample4
#sample5
build.gradle
・・・
//mainClassName = 'sample.PublisherSample1'
mainClassName = 'sample.PublisherSample2'

Reactor で skip・take 処理

Bacon.js で skip・take 処理」と同様の処理を Reactor を使用し Java 8 で実装してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20150104-2/

はじめに

今回は Gradle を使ってビルド・実行するため、下記のような build.gradle を用意しました。

build.gradle
apply plugin: 'application'

repositories {
    maven { url 'http://repo.spring.io/libs-snapshot' }
    jcenter()
}

dependencies {
    compile 'io.projectreactor:reactor-core:2.0.0.M2'
    runtime 'org.slf4j:slf4j-nop:1.7.9'
}

mainClassName = 'sample.Sample1'
//mainClassName = 'sample.Sample2'

単純な処理

ストリームへ送信された sample1 ~ sample6 をそのまま出力する処理を実装しました。 Broadcaster を使えばストリームへ要素を送信する事ができます。

Sample1.java
package sample;

import reactor.Environment;
import reactor.rx.Promise;
import reactor.rx.Streams;
import reactor.rx.stream.Broadcaster;

public class Sample1 {
    public static void main(String... args) throws Exception {
        Environment env = new Environment();

        Broadcaster<String> stream = Streams.broadcast(env);

        Promise<String> promise = stream.observe(System.out::println).next();

        Streams.range(1, 6).consume( i -> stream.onNext("sample" + i) );

        promise.await();

        env.shutdown();
    }
}

実行結果は下記の通りです。

実行結果
> gradle -q run

sample1
sample2
sample3
sample4
sample5
sample6

skip・take 処理

次に、skip と take 処理ですが、Reactor には skip に該当する処理が見当たらなかったので、filterAtomicInteger を使って自前で実装してみました。

なお、下記 (1) の箇所のように next メソッドを使って Promise を取得した場合は take した最初の要素しか出力されない点に注意が必要です。

take した全ての要素を出力させるには、下記 (2) の箇所のように最後の要素を取得する tap メソッド (戻り値は TapAndControls<O>) を使ったり、全要素をリスト化して取得する toList メソッド (戻り値は Promise<java.util.List<O>>) 等を使ったりする必要がありました。

Sample2.java
package sample;

import reactor.Environment;
import reactor.rx.Promise;
import reactor.rx.Stream;
import reactor.rx.Streams;
import reactor.rx.stream.Broadcaster;
import reactor.rx.action.support.TapAndControls;

import java.util.concurrent.atomic.AtomicInteger;

public class Sample2 {
    public static void main(String... args) throws Exception {
        Environment env = new Environment();

        skipAndTakeSampleA(env);

        System.out.println("-----");

        skipAndTakeSampleB(env);

        env.shutdown();
    }

    private static void skipAndTakeSampleA(Environment env) throws Exception {
        Broadcaster<String> stream = Streams.broadcast(env);

        // (1) next を使うと take した最初の要素のみ出力
        Promise<String> promise = skip(stream, 2)
            .take(3)
            .map(s -> "#" + s)
            .observe(System.out::println)
            .next();

        Streams.range(1, 6).consume( i -> stream.onNext("sampleA-" + i) );

        promise.await();
    }

    private static void skipAndTakeSampleB(Environment env) throws Exception {
        Broadcaster<String> stream = Streams.broadcast(env);

        // (2) take した全要素を出力するには tap や toList 等を使う必要あり
        TapAndControls<String> tap = skip(stream, 2)
            .take(3)
            .map(s -> "#" + s)
            .observe(System.out::println)
            .tap();

        Streams.range(1, 6).consume( i -> stream.onNext("sampleB-" + i) );

        tap.get();
    }

    // skip 処理
    private static <T> Stream<T> skip(Stream<T> st, int num) {
        AtomicInteger counter = new AtomicInteger();

        return st.filter(s -> counter.incrementAndGet() > num);
    }
}

実行結果は下記の通りです。 (build.gradle の mainClassName を変更して実行)

実行結果
> gradle -q run

#sampleA-3
-----
#sampleB-3
#sampleB-4
#sampleB-5
build.gradle
・・・
//mainClassName = 'sample.Sample1'
mainClassName = 'sample.Sample2'

Bacon.js で skip・take 処理

リアクティブプログラミング用ライブラリの Bacon.js を Node.js 上で使用し、「RxJS で行単位のファイル処理」 で試したような skip・take 処理のサンプルを実装してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20150104/

はじめに

npm でインストールするために下記のような package.json を用意します。 今回は CoffeeScript で実装するので coffee-script も追加しています。

package.json
{
  ・・・
  "dependencies": {
    "baconjs": "*",
    "coffee-script": "*"
  }
}

npm install でインストールします。

インストール
> npm install

Bus の利用

まずは Bus を使って、ストリームへ push した内容をそのままログ出力する処理を実装してみます。

Bus は後から要素を push できるストリームですが、Bacon.js には他にも色々とストリームを作る方法が用意されています。 (下記は一部)

  • 配列からストリームを作る Bacon.fromArray
  • コールバック関数を使ってストリームを作る Bacon.fromCallback, Bacon.fromNodeCallback (Node.js 用のコールバック関数へ適用)
  • 任意のストリームを作る Bacon.fromBinder
sample1.coffee
Bacon = require('baconjs').Bacon

bus = new Bacon.Bus()

bus.log()

[1..6].forEach (i) -> bus.push "sample#{i}"

bus.end()

実行結果は下記の通りです。 Bus へ push した sample1 ~ sample6 を順次出力しています。

実行結果
> coffee sample1.coffee

sample1
sample2
sample3
sample4
sample5
sample6
<end>

skip・take 処理

次に、下記のような加工処理を追加してみました。

  • (1) 2つの要素を無視 (skip)
  • (2) 3つの要素だけを取得 (take)
  • (3) 先頭に # を付ける (map)
sample2.coffee
Bacon = require('baconjs').Bacon

bus = new Bacon.Bus()

bus.skip(2).take(3).map((s) -> "##{s}").log()

[1..6].forEach (i) -> bus.push "sample#{i}"

bus.end()

実行結果は下記の通りです。

実行結果
> coffee sample2.coffee

#sample3
#sample4
#sample5
<end>