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 へパースする関数は下記のような種類があります。

一見どれを使えばよいか分からなくなってしまいそうですが、xmlInternalTreeParsexmlNativeTreeParsexmlParsexmlTreeParse で useInternalNodes を TRUE に指定した場合と同じです。 (つまり xmlTreeParse が処理の本体で、残りは useInternalNodes のデフォルト値を TRUE に変えただけ)

関数 useInternalNodes パラメータのデフォルト値
xmlTreeParse FALSE
xmlInternalTreeParse TRUE
xmlNativeTreeParse TRUE
xmlParse TRUE

ここで、useInternalNodes を TRUE に指定すると xpathApplygetNodeSet のような 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でリソースファイルの出力先を変更

GradleJava プラグインを使う場合、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 の単体実行)

不都合の生じるケースとして、CDIJava 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