Gradle の Scala プラグインで -Xprint オプションを使用
Gradle の Scala プラグインで -Xprint オプションを試してみました。
- Gradle 1.7
-Xprint はコンパイル途中のコードを出力する Scala コンパイラのオプションで、
-Xprint:<フェーズ>
のようにコンパイルフェーズを指定して使用します。
例えば -Xprint:typer
と指定する事で implicit による暗黙変換などを処理した後のコードが出力されます。
-Xprint オプション指定方法
Gradle の Scala プラグインで -Xprint のようなオプションを指定するには下記のように compileScala.scalaCompileOptions.additionalParameters
を使います。
build.gradle 設定例 (-Xprint:typer 指定)
apply plugin: 'scala' ・・・ compileScala { scalaCompileOptions.additionalParameters = ['-Xprint:typer', ・・・] }
ここで compileScala.scalaCompileOptions.useAnt
の値によって、使用される Scala コンパイラクラスや -Xprint の出力に差が生じる点に注意が必要です。
useAnt の値 | Scala コンパイラクラス | -Xprint の出力 |
---|---|---|
true | AntScalaCompiler | gradle コマンドで -i オプションを指定すると出力される |
false | ZincScalaCompiler | 特に何もしなくても出力される |
なお、useAnt は true
がデフォルト値です。
-Xprint:typer を指定したビルド例
それでは、下記のような build.gradle とサンプルソースを使って -Xprint:typer を試してみます。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130928/
ビルド定義 build.gradle
apply plugin: 'scala' repositories { mavenCentral() } dependencies { compile 'org.scala-lang:scala-library:2.10.2' compile 'org.scalaz:scalaz-core_2.10:7.1.0-M3' } compileScala { scalaCompileOptions.additionalParameters = ['-Xprint:typer', '-feature'] // (1) scalaCompileOptions.useAnt = false // (2) // scalaCompileOptions.useAnt = true }
サンプルソース Sample.scala
package fits.sample import scala.language.postfixOps import scalaz._ import Scalaz._ object Sample extends App { val plus3: Int => Int = 3 + val times: Int => Int = 2 * // 2 * (3 + 4) = 14 println( 4 |> plus3 >>> times ) // (3 + 4, 2 * 5) = (7, 10) println( (4, 5) |> plus3 *** times ) // (3 + 5, 2 * 5) = (8, 10) println( 5 |> plus3 &&& times ) }
(1) useAnt = false の場合
useAnt を false
に変更した場合の gradle build
結果は下記の通りです。
(-Xprint:typer の内容が出力されます)
実行結果
> gralde build :compileJava UP-TO-DATE :compileScala [[syntax trees at end of typer]] // Sample.scala package fits.sample { import scala.language.postfixOps; import scalaz._; import scalaz.Scalaz._; object Sample extends AnyRef with App { def <init>(): fits.sample.Sample.type = { Sample.super.<init>(); () }; private[this] val plus3: Int => Int = { ((x: Int) => 3.+(x)) }; <stable> <accessor> def plus3: Int => Int = Sample.this.plus3; private[this] val times: Int => Int = { ((x: Int) => 2.*(x)) }; <stable> <accessor> def times: Int => Int = Sample.this.times; scala.this.Predef.println(scalaz.Scalaz.ToIdOps[Int](4).|>[Int](scalaz.Scalaz.ToComposeOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).>>>[Int](Sample.this.times))); scala.this.Predef.println(scalaz.Scalaz.ToIdOps[(Int, Int)](scala.Tuple2.apply[Int, Int](4, 5)).|>[(Int, Int)](scalaz.Scalaz.ToArrowOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).***[Int, Int](Sample.this.times))); scala.this.Predef.println(scalaz.Scalaz.ToIdOps[Int](5).|>[(Int, Int)](scalaz.Scalaz.ToArrowOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).&&&[Int](Sample.this.times))) } } :processResources UP-TO-DATE :classes :jar :assemble :compileTestJava UP-TO-DATE :compileTestScala UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build BUILD SUCCESSFUL
(2) useAnt = true の場合
useAnt が true
(デフォルト値) の場合の gradle build
結果は下記の通りです。
(-Xprint:typer の内容は出力されません)
実行結果1
> gralde build :compileJava UP-TO-DATE :compileScala :processResources UP-TO-DATE :classes :jar :assemble :compileTestJava UP-TO-DATE :compileTestScala UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build BUILD SUCCESSFUL
ここで gradle build -i
と実行すれば -Xprint:typer の内容が出力されます。
実行結果2
> gralde build -i ・・・ [ant:scalac] [[syntax trees at end of typer]] // Sample.scala [ant:scalac] package fits.sample { [ant:scalac] import scala.language.postfixOps; [ant:scalac] import scalaz._; [ant:scalac] import scalaz.Scalaz._; [ant:scalac] object Sample extends AnyRef with App { [ant:scalac] def <init>(): fits.sample.Sample.type = { [ant:scalac] Sample.super.<init>(); [ant:scalac] () [ant:scalac] }; [ant:scalac] private[this] val plus3: Int => Int = { [ant:scalac] ((x: Int) => 3.+(x)) [ant:scalac] }; [ant:scalac] <stable> <accessor> def plus3: Int => Int = Sample.this.plus3; [ant:scalac] private[this] val times: Int => Int = { [ant:scalac] ((x: Int) => 2.*(x)) [ant:scalac] }; [ant:scalac] <stable> <accessor> def times: Int => Int = Sample.this.times; [ant:scalac] scala.this.Predef.println(scalaz.Scalaz.ToIdOps[Int](4).|>[Int](scalaz.Scalaz.ToComposeOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).>>>[Int](Sample.this.times))); [ant:scalac] scala.this.Predef.println(scalaz.Scalaz.ToIdOps[(Int, Int)](scala.Tuple2.apply[Int, Int](4, 5)).|>[(Int, Int)](scalaz.Scalaz.ToArrowOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).***[Int, Int](Sample.this.times))); [ant:scalac] scala.this.Predef.println(scalaz.Scalaz.ToIdOps[Int](5).|>[(Int, Int)](scalaz.Scalaz.ToArrowOps[Function1, Int, Int](Sample.this.plus3)(scalaz.Scalaz.function1Instance).&&&[Int](Sample.this.times))) [ant:scalac] } [ant:scalac] } [ant:scalac] :compileScala (Thread[main,5,main]) - complete ・・・
R による XML の CSV 化
R を使って XML の内容 (特定の要素のみ) を CSV ファイルへ出力してみました。
- R 3.0.1
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130922/
CSV 化の対象 XML は下記で、VALUE 要素の属性とテキストノード値を CSV 出力する事にします。
対象 XML ファイル data.xml
<?xml version="1.0" encoding="UTF-8"?> <GET_STATS_DATA> <STATISTICAL_DATA> <DATA_INF> <VALUE tab="20" cat01="10" cat02="0010" cat03="020" time="2013000000">15</VALUE> <VALUE tab="20" cat01="20" cat02="0010" cat03="020" time="2013000000">27</VALUE> <VALUE tab="20" cat01="30" cat02="0010" cat03="020" time="2013000000">36</VALUE> <VALUE tab="20" cat01="40" cat02="0010" cat03="020" time="2013000000">66</VALUE> <VALUE tab="20" cat01="50" cat02="0010" cat03="020" time="2013000000">47</VALUE> </DATA_INF> </STATISTICAL_DATA> </GET_STATS_DATA>
かなり簡略化してますが、次世代統計利用システム API で取得したデータがこのような構造になっていました。
XML パッケージのインストール
まずは R に XML パッケージをインストールしておきます。
packages.install("XML")
XML のパース関数
XML を DOM へパースする関数は下記のような種類があります。
一見どれを使えばよいか分からなくなってしまいそうですが、xmlInternalTreeParse
・xmlNativeTreeParse
・xmlParse
は xmlTreeParse
で useInternalNodes を TRUE に指定した場合と同じです。 (つまり xmlTreeParse が処理の本体で、残りは useInternalNodes のデフォルト値を TRUE に変えただけ)
関数 | useInternalNodes パラメータのデフォルト値 |
---|---|
xmlTreeParse | FALSE |
xmlInternalTreeParse | TRUE |
xmlNativeTreeParse | TRUE |
xmlParse | TRUE |
ここで、useInternalNodes を TRUE に指定すると xpathApply
や getNodeSet
のような XPath 式を使う関数を使える C レベルの XML ノードが戻り値で返ってきます。
ちなみに、SAX を処理する関数は xmlEventParse
です。
1. データフレーム化して CSV 出力
まずは、下記のような手順でデータフレームを作って CSV 出力してみました。
- (1) xmlParse で DOM へパース
- (2) getNodeSet で XPath 式を使って VALUE 要素のリストを取得
- (3) sapply で属性やテキストノード値を個々にベクトル化 (xmlGetAttr で属性値を取得、xmlValue でテキストノード値を取得)
- (4) data.frame で (3) をデータフレーム化
- (5) write.csv で (4) の内容を CSV ファイルへ出力
なお、テキストノード値は strtoi
で integer へ変換しています。
sample.R
library(XML) doc <- xmlParse("data.xml") items <- getNodeSet(doc, "//VALUE") tab <- sapply(items, function(x) xmlGetAttr(x, "tab")) cat01 <- sapply(items, function(x) xmlGetAttr(x, "cat01")) cat02 <- sapply(items, function(x) xmlGetAttr(x, "cat02")) cat03 <- sapply(items, function(x) xmlGetAttr(x, "cat03")) time <- sapply(items, function(x) xmlGetAttr(x, "time")) value <- sapply(items, function(x) strtoi(xmlValue(x))) df <- data.frame(tab, cat01, cat02, cat03, time, value, stringsAsFactors = FALSE) write.csv(df, file = "data.csv", row.names = FALSE)
なお、write.csv
で row.names = FALSE としていますが、row.names = TRUE のままでは行番号の列が出力されてしまうのでご注意ください。
実行例
> R CMD BATCH sample.R
出力結果は下記の通りです。 integer に変換した value 列は " で囲まれずに出力されています。
出力結果 data.csv
"tab","cat01","cat02","cat03","time","value" "20","10","0010","020","2013000000",15 "20","20","0010","020","2013000000",27 "20","30","0010","020","2013000000",36 "20","40","0010","020","2013000000",66 "20","50","0010","020","2013000000",47
2. 行列を CSV 出力
次は、データフレームを使わずに sapply
で行列を作って CSV 出力してみました。
なお、下記のように sapply
した際の結果は tab や cat01 等が行となってしまうので(行と列が逆)、t
で転置行列化して CSV 出力しています。
ちなみに、このサンプルでは strtoi
は無意味なので使っていません。
sample2.R
library(XML) doc <- xmlParse("data.xml") items <- getNodeSet(doc, "//VALUE") d <- sapply(items, function(x) list( tab = xmlGetAttr(x, "tab"), cat01 = xmlGetAttr(x, "cat01"), cat02 = xmlGetAttr(x, "cat02"), cat03 = xmlGetAttr(x, "cat03"), time = xmlGetAttr(x, "time"), value = xmlValue(x) )) write.csv(t(d), file = "data2.csv", row.names = FALSE)
実行例
> R CMD BATCH sample2.R
出力結果は下記の通りです。
出力結果 data2.csv
"tab","cat01","cat02","cat03","time","value" 20,10,0010,020,2013000000,15 20,20,0010,020,2013000000,27 20,30,0010,020,2013000000,36 20,40,0010,020,2013000000,66 20,50,0010,020,2013000000,47
3. SAX で CSV 出力
最後に SAX で CSV 出力してみました。
VALUE 要素のテキストノードを処理するために .state
の値を制御フラグとして使っています。
また、write.csv
は使わずに cat
を使ってファイル出力しました。
sample3.R
library(XML) f <- file("data3.csv", "w") cat('"tab","cat01","cat02","cat03","time","value"\n', file = f, append = TRUE) # VALUE 要素の属性を処理 procValueNode <- function(name, attrs, .state) { if (name == "VALUE") { cat(attrs[1], attrs[2], attrs[3], attrs[4], attrs[5], "", file = f, sep = ",", append = TRUE) # procValueText で処理させるために .state を TRUE に変更 .state = TRUE } .state } # VALUE 要素のテキストノードを処理 procValueText <- function(content, .state) { if (.state) { cat(content, "\n", file = f, sep = "", append = TRUE) } # .state を FALSE とするために FALSE を返す FALSE } xmlEventParse("data.xml", handlers = list( startElement = procValueNode, text = procValueText ), state = FALSE) close(f)
実行例
> R CMD BATCH sample3.R
出力結果は 2. と同じです。
出力結果 data3.csv
"tab","cat01","cat02","cat03","time","value" 20,10,0010,020,2013000000,15 20,20,0010,020,2013000000,27 20,30,0010,020,2013000000,36 20,40,0010,020,2013000000,66 20,50,0010,020,2013000000,47
処理時間の比較
10万行の CSV を出力するようなデータを処理した場合の処理時間は概ね下記のようになりました。比較のために Groovy で XmlSlurper や StAX を使った場合も試してみました。
タイプ | スクリプト | 処理時間 |
---|---|---|
1. データフレーム化して csv 出力 | sample.R | 40秒 |
2. 行列を CSV 出力 | sample2.R | 40秒 |
3. SAX で CSV 出力 | sample3.R | 14秒 |
Groovy で XmlSlurper 利用 | sample_xmlslurper.groovy | 12秒 |
Groovy で StAX 利用 | sample_stax.groovy | 6秒 |
R の中ではやはり SAX を用いた方が高速でしたが、普通に Groovy で処理した方が速かったので、今回のようなケースを R で処理する必然性は低いかもしれません。
なお、使用したGroovy スクリプトは下記の通りです。
sample_xmlslurper.groovy
def doc = new XmlSlurper().parse(new File(args[0])) println '"tab","cat01","cat02","cat03","time","value"' doc.STATISTICAL_DATA.DATA_INF.VALUE.each { println "${it.@tab},${it.@cat01},${it.@cat02},${it.@cat03},${it.@time},${it.text()}" }
sample_stax.groovy
import javax.xml.stream.* def factory = XMLInputFactory.newInstance() def xr = factory.createXMLStreamReader(new File(args[0]).newReader("UTF-8")) def procValueNode = { stream -> if (stream.name.localPart == 'VALUE') { def items = (0..<stream.attributeCount).collect { stream.getAttributeValue(it) } items << stream.elementText println items.join(',') } } println '"tab","cat01","cat02","cat03","time","value"' while(xr.hasNext()) { switch (xr.eventType) { case XMLStreamConstants.START_ELEMENT: procValueNode(xr) break } xr.next() } xr.close()
Gradleでリソースファイルの出力先を変更
Gradle で Java プラグインを使う場合、main のクラスファイルやリソースファイルの出力先は下記のようになっています。
Gradle 1.7
ファイル種類 | 出力先の設定 | デフォルト値 |
---|---|---|
クラスファイル | sourceSets.main.output.classesDir | build/classes/main |
リソースファイル | sourceSets.main.output.resourcesDir | build/resources/main |
Maven とは違ってクラスファイルとリソースファイルを別々のディレクトリへ出力するようになっていますが、この動作で不都合の生じる場合は下記のようにしてリソースファイルの出力先を変更する事になります。
sourceSets.main.output.resourcesDir = ・・・
sourceSets { main { output.resourcesDir = ・・・ } }
リソースファイル出力先の変更例 (Weld の単体実行)
不都合の生じるケースとして、CDI (Java Contents and Dependency Injection) の参照実装である Weld を Gradle 上で単体実行するケースが挙げられます。
Weld の単体実行は META-INF/beans.xml の配置されているディレクトリから DI 対象のクラスを探す
ので、クラスファイルとリソースファイルの配置場所が異なっていると DI 対象のクラスを見つけられず CDI が正常に機能しません。
この場合、下記のように sourceSets.main.output.classesDir の値を sourceSets.main.output.resourcesDir へ設定し、リソースファイルをクラスファイルと同じディレクトリへ出力させれば問題は解決します。
リソースファイルの出力先変更例 (build.gradle)
apply plugin: 'java' repositories { mavenCentral() } dependencies { compile 'org.jboss.weld.se:weld-se-core:2.0.3.Final' compile 'org.slf4j:slf4j-simple:1.7.5' } sourceSets { main { // クラスファイルと同じディレクトリへリソースファイルを出力する設定 output.resourcesDir = output.classesDir } } task run(dependsOn: 'build') << { // Weld の単体実行 javaexec { main = 'org.jboss.weld.environment.se.StartMain' classpath = runtimeClasspath } }
実行例
> gradle run :compileJava :processResources :classes :jar :assemble :compileTestJava UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build :run [main] INFO org.jboss.weld.Version - WELD-000900 2.0.3 (Final) ・・・
補足
上記 Weld 単体実行のケースは、生成された JAR ファイルをクラスパスへ追加する事でも対応可能です。(この場合、リソースファイルの出力先変更は不要)
クラスパスへ JAR を追加して対応する例 (build.gradle)
apply plugin: 'java' repositories { mavenCentral() } dependencies { compile 'org.jboss.weld.se:weld-se-core:2.0.3.Final' compile 'org.slf4j:slf4j-simple:1.7.5' } task run(dependsOn: 'build') << { // Weld の単体実行 javaexec { main = 'org.jboss.weld.environment.se.StartMain' // 生成された JAR ファイルをクラスパスへ追加 classpath = runtimeClasspath + files(jar.archivePath) } }
Vert.x 2.0 で Scala 言語モジュールを使用
Vert.x 2.0 用の Scala 言語モジュールを使って簡単な HTTP サーバーを実装してみます。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130901_4/
Scala 言語モジュールのビルド
Vert.x 用の Scala 言語モジュールは今のところ Vert.x デフォルト設定の Maven リポジトリからは取得できないようですので、今回はソースからビルドする事にしました。
git でソースを取得し、ソースに含まれている gradlew コマンドを使ってビルドします。
> git clone https://github.com/vert-x/mod-lang-scala.git ・・・ > cd mod-lang-scala > gradlew install ・・・ :install BUILD SUCCESSFUL
上記のように gradlew install とする事で、ローカルの Maven リポジトリへ Scala の言語モジュールがインストールされます。(.m2/repository/io/vertx/lang-scala/2.0.0-SNAPSHOT)
Scala による単純な HTTP サーバー
今回は下記のような単純な HTTP サーバー処理を実装してみました。
SampleServer.scala
import org.vertx.scala.core.Future import org.vertx.scala.core.http.HttpServerRequest import org.vertx.scala.platform.Verticle class SampleServer extends Verticle { override def start(future: Future[Void]): Unit = { start() vertx.newHttpServer.requestHandler { req: HttpServerRequest => req.response.end("test data") }.listen(8080) } }
今の Scala 言語モジュールは Scala ソースを直接実行できないようなので、scalac 等を使ってコンパイルしておきます。
コンパイルには下記のような JAR を CLASSPATH に指定します。
- lang-scala-2.0.0-SNAPSHOT.jar
- vertx-core-2.0.1-final.jar
- vertx-platform-2.0.1-final.jar
- netty-all-4.0.7.Final.jar
lang-scala-2.0.0-SNAPSHOT.jar は Scala 言語モジュールのビルド時に build/libs ディレクトリへ生成されており、他の JAR は Vert.x の実行環境に含まれています。
コンパイル例
> scalac -cp ・・・lang-scala-2.0.0-SNAPSHOT.jar;・・・netty-all-4.0.7.Final.jar; SampleServer.scala
実行
実行には、予め langs.properties ファイルへ Scala 言語モジュールの設定を行っておく必要があります。
Vert.x の conf/langs.properties へ追記すれば良さそうな気がしますが、現時点ではこのファイルに追記しても効果は無いようです。
ソースを見たところ CLASSPATH 上の langs.properties を参照するようですので、今回はカレントディレクトリへ下記のような langs.properties ファイルを作成する事にしました。
langs.properties
scala=io.vertx~lang-scala~2.0.0-SNAPSHOT:org.vertx.scala.platform.impl.ScalaVerticleFactory
これで vertx run scala:<クラス名>
を実行すると、Maven のローカルリポジトリから Vert.x の sys-mods ディレクトリへ Scala 言語モジュールがインストールされ(初回のみ)、処理が実行されます。
実行例
> vertx run scala:SampleServer Module io.vertx~lang-scala~2.0.0-SNAPSHOT successfully installed
ちなみに langs.properties へ .=scala
と1行追記しておけば(デフォルトを scala に設定)、vertx run 時に scala: を省略できるようになります。
Frege 上で Java クラスを使用する
前回、Frege で Functor や Applicative を試しましたが、今回は Frege のソース内で Java クラスを使用してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130901_3/
はじめに
一部の Java クラスは初めから Frege のソース内で使用できるようになっていますが、それ以外の Java クラスを使用するには、下記のような data 宣言を行う必要があります。(厳密な定義は Frege の仕様書をご覧ください)
data <データタイプ名> <型変数・・・> = [mutable | pure] native <Javaタイプ名> where [pure] native <関数名> [<Javaメソッド名など>] :: <型シグネチャ・・・> ・・・
補足事項は下記。
- Immutable(不変)クラスや副作用の無いメソッドに対しては pure native を指定
- pure native では無いメソッド(オブジェクトの状態を変化させたりするメソッド等)に対しては IO や ST モナドを返すようにする
- Java インスタンスメソッドを使う場合は第一引数を自身のデータタイプとする
- "関数名" と "Javaメソッド名" が等しい場合は "Javaメソッド名" を省略可能
Immutable(不変)クラスの場合
まずは Java の Immutable クラスを使用する場合です。
java.math.BigDecimal クラスを題材に、下記のような処理を Frege で実装してみました。(ちなみに java.math.BigInteger の方は Integer として Frege 内で使えます)
- (1) 文字列を引数に取るコンストラクタ
- (2) toString メソッド
- (3) add メソッド
- (4) add メソッド(
+
関数として使用) - (5) multiply メソッド(
*
関数として使用)
bigdecimal_sample.fr
package sample.BigDecimalSample where data BigDecimal = pure native java.math.BigDecimal where -- (1) pure native new :: String -> BigDecimal -- (2) pure native toString :: BigDecimal -> String -- (3) pure native add :: BigDecimal -> BigDecimal -> BigDecimal -- (4) pure native (+) add :: BigDecimal -> BigDecimal -> BigDecimal -- (5) pure native (*) multiply :: BigDecimal -> BigDecimal -> BigDecimal main args = do let num1 = BigDecimal.new "100" let num2 = BigDecimal.new "50" putStrLn $ (num1.add num2).toString putStrLn $ (num1.+ num2).toString putStrLn $ (num1.* num2).toString
実行結果
> java -cp .;fregec.jar sample.BigDecimalSample 150 150 5000 runtime ・・・
Num の型インスタンス
上記処理で num1.+ num2
では無く num1 + num2
とするには BigDecimal を Num の型インスタンス宣言してやる必要があります。
今回、Num の型インスタンス宣言を行うには下記のような関数定義が必要でした。
- (+)
- (-)
- (*)
- one
- zero
- fromInt
- hashCode
- <=>
bigdecimal_sample2.fr (Num 型インスタンス宣言版)
package sample.BigDecimalSample2 where data BigDecimal = pure native java.math.BigDecimal where pure native new :: String -> BigDecimal pure native toString :: BigDecimal -> String pure native zero java.math.BigDecimal.ZERO :: BigDecimal pure native one java.math.BigDecimal.ONE :: BigDecimal pure native (+) add :: BigDecimal -> BigDecimal -> BigDecimal pure native (-) subtract :: BigDecimal -> BigDecimal -> BigDecimal pure native (*) multiply :: BigDecimal -> BigDecimal -> BigDecimal pure native hashCode :: BigDecimal -> Int pure native compareTo :: BigDecimal -> BigDecimal -> Int instance Ord BigDecimal where a <=> b = (a.compareTo b).<=> 0 instance Num BigDecimal where pure native fromInt java.math.BigDecimal.valueOf :: Int -> BigDecimal main args = do let num1 = BigDecimal.new "100" let num2 = BigDecimal.new "50" putStrLn $ (num1 + num2).toString putStrLn $ (num1 * num2).toString putStrLn $ (num1 - num2).toString
実行結果
> java -cp .;fregec.jar sample.BigDecimalSample2 150 5000 50 runtime ・・・
Mutable(可変)クラスの場合
次は Java の Mutable クラスを使用する場合です。
題材として良さそうなクラスが思い浮かばなかったので、とりあえず java.awt.Point を使ってみました。
Mutable クラスの場合は基本的に pure を付けない native な関数を定義して IO や ST モナドを返すようにします。
IO モナド版
まずは IO モナド版です。
new や toString 等で IO <データタイプ> を返すようにします。(今回は IO Point)
Java のインスタンスメソッドに対する関数(toString や move 等)の第一引数は IO モナドでは無く普通の値(今回の場合は Point)となります。
とりあえず下記のような処理を実装してみました。
- (1) new で x=10, y=20 へ設定
- (2) move で x=20, y=30 へ移動
- (3) translate で x を +5、y を +3
point_sample.fr (IOモナド版)
package sample.PointSample where data Point = mutable native java.awt.Point where native new :: Int -> Int -> IO Point native toString :: Point -> IO String native move :: Point -> Int -> Int -> IO () native translate :: Point -> Int -> Int -> IO () main args = do -- (1) p <- Point.new 10 20 -- (2) p.move 20 30 -- (3) p.translate 5 3 p.toString >>= putStrLn
実行結果
> java -cp .;fregec.jar sample.PointSample java.awt.Point[x=25,y=33] ・・・
ST モナド版
次に ST モナド版です。
new が ST s (Mutable s Point) を返し、toString 等の第一引数が Mutable s Point となっている点が IO モナド版との違いです。
point_sample2.fr (STモナド版)
package sample.PointSample2 where data Point = mutable native java.awt.Point where native new :: Int -> Int -> ST s (Mutable s Point) native toString :: Mutable s Point -> ST s String native move :: Mutable s Point -> Int -> Int -> ST s () native translate :: Mutable s Point -> Int -> Int -> ST s () sample :: Mutable s Point -> ST s String sample p = do p.move 20 30 p.translate 5 3 p.toString main args = do Point.new 10 20 >>= sample >>= putStrLn
実行結果
> java -cp .;fregec.jar sample.PointSample2 java.awt.Point[x=25,y=33] ・・・
補足
move や translate 等のオブジェクトの状態を変化させるようなメソッドを一切使わないのであれば java.awt.Point を pure native とする事も可能です。
point_sample3.fr
package sample.PointSample3 where data Point = pure native java.awt.Point where pure native new :: Int -> Int -> Point pure native toString :: Point -> String main args = do let p = Point.new 10 20 putStrLn $ p.toString
Netty 4 で WebSocket
以前、Jetty, Grizzly, Netty(3.2.4), EM-WebSocket を使って WebSocket の簡単なサンプルを実装しましたが、今回は Netty 4.0.7 を使って同様の処理を実装してみました。
- Netty 4.0.7 Final
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130901_2/
WebSocket クライアント
WebSocket のクライアントは下記のように以前と基本的に同じです。 以前は Chrome でしか動作確認できませんでしたが、今では Firefox でも問題なく動作します。
index.html (WebSocket クライアント)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <script> var ws = new WebSocket("ws://localhost:8080/sample"); ws.onopen = function(event) { console.log("websocket open"); stateChange("opened") }; ws.onmessage = function(event) { document.getElementById("log").innerHTML += "<li>" + event.data + "</li>"; }; ws.onclose = function(event) { console.log("websocket close"); stateChange("closed") }; ws.onerror = function(event) { console.log("error"); stateChange("error") }; function sendMessage() { var msg = document.getElementById("message").value; ws.send(msg); } function stateChange(state) { document.getElementById("state").innerHTML = state; } </script> </head> <body> <input id="message" type="text" /> <input type="button" value="send" onclick="sendMessage()" /> <span id="state">closed</span> <ul id="log"></ul> </body> </html>
Netty 4 による WebSocket サーバー1
以前はハンドシェイク処理を自前で実装する必要がありましたが、Netty 4 では下記のようなパイプラインを構成すれば、割と簡単に WebSocket を処理できるようになっています。
- (1) HttpServerCodec (HttpRequestDecoder + HttpResponseEncoder でも代替可能)
- (2) HttpObjectAggregator
- (3) WebSocketServerProtocolHandler
- (4) WebSocket を処理する自前のハンドラー
echo_server.groovy
@Grab('io.netty:netty-all:4.0.7.Final') import io.netty.bootstrap.ServerBootstrap import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInitializer import io.netty.channel.SimpleChannelInboundHandler import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.socket.SocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.handler.codec.http.HttpObjectAggregator import io.netty.handler.codec.http.HttpServerCodec import io.netty.handler.codec.http.websocketx.TextWebSocketFrame import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler // (4) の実装 class SampleHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { // WebSocket 処理 @Override void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) { println "read: ${frame}" // flush が必要なので writeAndFlush を使用 ctx.channel().writeAndFlush(new TextWebSocketFrame("echo : ${frame.text()}")) } } class SampleChannelInitializer extends ChannelInitializer<SocketChannel> { @Override void initChannel(SocketChannel ch) { ch.pipeline().addLast( // (1) new HttpServerCodec(), // (2) new HttpObjectAggregator(65536), // (3) new WebSocketServerProtocolHandler("/sample"), // (4) new SampleHandler() ) } } def b = new ServerBootstrap() def bgroup = new NioEventLoopGroup() def wgroup = new NioEventLoopGroup() try { b.group(bgroup, wgroup) .channel(NioServerSocketChannel) .childHandler(new SampleChannelInitializer()) def ch = b.bind(8080).sync().channel() println "start ..." ch.closeFuture().sync() } finally { bgroup.shutdownGracefully() wgroup.shutdownGracefully() }
実行例
> groovy echo_server.groovy start ...
Netty 4 による WebSocket サーバー2
WebSocketServerProtocolHandler の代わりに自前のハンドラーで WebSocketServerHandshakerFactory・WebSocketServerHandshaker を使えば、下記のような細かい制御も可能です。
echo_server2.groovy
@Grab('io.netty:netty-all:4.0.7.Final') import io.netty.bootstrap.ServerBootstrap import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInitializer import io.netty.channel.ChannelInboundHandlerAdapter import io.netty.channel.socket.SocketChannel import io.netty.handler.codec.http.HttpObjectAggregator import io.netty.handler.codec.http.HttpServerCodec import io.netty.handler.codec.http.FullHttpRequest import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame import io.netty.handler.codec.http.websocketx.TextWebSocketFrame import io.netty.handler.codec.http.websocketx.WebSocketFrame import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory class SampleHandler extends ChannelInboundHandlerAdapter { private WebSocketServerHandshaker ws @Override void channelRead(ChannelHandlerContext ctx, Object msg) { println "read: ${msg}" if (msg instanceof FullHttpRequest) { handleHttp(ctx, msg) } else if (msg instanceof WebSocketFrame) { handleWebSocket(ctx, msg) } } @Override void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush() } private void handleHttp(ChannelHandlerContext ctx, FullHttpRequest req) { def factory = new WebSocketServerHandshakerFactory('ws://localhost:8080/sample', null, false) ws = factory.newHandshaker(req) if (ws == null) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()) } else { // ハンドシェイク処理 ws.handshake(ctx.channel(), req) } } // WebSocket 処理 private void handleWebSocket(ChannelHandlerContext ctx, WebSocketFrame frame) { if (frame instanceof CloseWebSocketFrame) { ws.close(ctx.channel(), frame.retain()) } else if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException("not supported : ${frame.getClass()}") } else { ctx.channel().write(new TextWebSocketFrame("echo : ${frame.text()}")) } } } class SampleChannelInitializer extends ChannelInitializer<SocketChannel> { @Override void initChannel(SocketChannel ch) { ch.pipeline().addLast( new HttpServerCodec(), new HttpObjectAggregator(65536), new SampleHandler() ) } } def b = new ServerBootstrap() ・・・
実行例
> groovy echo_server2.groovy start ...
Java SE 8 で関数合成
以前 に Groovy や Scala 等で実施した関数合成と同様の処理を Java 8 で試してみました。
- Java SE 8 EA b102 (現時点の最新は b104 のようです)
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130901/
andThen と compose
Java 8 では Scala と同様に andThen や compose メソッドを使います。 処理の実行にも apply メソッドを使いますが、Scala のようなメソッド名の省略はできません。
なお、andThen・compose メソッドは java.util.function.Function インターフェース等にデフォルト実装されています。
ComposeSample.java
import java.util.function.Function; public class ComposeSample { public static void main(String... args) { Function<Integer, Integer> plus = (x) -> x + 3; Function<Integer, Integer> times = (x) -> x * 2; // (4 + 3) * 2 = 14 : plus -> times System.out.println(plus.andThen(times).apply(4)); // (4 * 2) + 3 = 11 : times -> plus System.out.println(plus.compose(times).apply(4)); } }
今回のような用途には Function<Integer, Integer> の代わりに IntFunction を使いたいところですが、今のところ IntFunction は Function を extends しておらず andThen・compose メソッドは使えないようです。
実行結果
> java ComposeSample 14 11