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
この結果より、平均種子数の予測値を求める関数は となります。
予測線の描画1
次に、ポアソン回帰の結果を使って種子数 y の予測線を描画し PNG ファイルへ出力してみます。
まず、種子数 y の予測値を算出するために、data3a.csv における x の最小値 7.19 から最大値 12.4 までを 0.1 刻みにした配列 xx
を作成しました。
coef
関数を使えば glm の結果から Coefficients の Estimate 値を配列として取得できるので、これと xx
を使って種子数 y の予測値を算出できます。
plot
と layer
を組み合わせる事で複数のグラフを重ねて描画できます。
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)
ここで、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 内では のような計算が実施されます。
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)
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 のソースファイルをパースしてみました。
- Java SE 8u31
前回 と同じように、ソースファイルを 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.JavaCompiler
と com.sun.tools.javac.main.JavaCompiler
は別物なのでご注意ください。
StandardJavaFileManager
の getJavaFileObjects
メソッドはソースファイル名を可変長引数の文字列で与えれば 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.JavaCompiler
の getTask
メソッドで取得した 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.val
や lombok.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 内の var
を java.lang.Object
へ変更するように実装します。
CompilationUnitTree
(AST) は Visitor パターンで処理できるようになっています。
今回は var を使っている変数定義の部分を処理させたいだけなので、TreeScanner
の visitVariable
メソッドをオーバーライドした内部クラス (VarVisitor
) を用意しました。
JCVariableDecl
の vartype
フィールドに変数の型が設定されており、これを変更すれば変数の型が変わります。
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) のように ActorSystem
と FlowMaterializer
オブジェクトを作成し、処理完了時に 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.loglevel
を off
にしてログ出力を無効化する事もできますが、そうするとエラーログすら出力されなくなるのでご注意下さい。
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.java を Flow
を使って書き換えると下記のようになります。
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 に該当する処理が見当たらなかったので、filter
と AtomicInteger
を使って自前で実装してみました。
なお、下記 (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>