Scala のケースクラスに制約を持たせる

Scala のケースクラスで値に制約を持たせたい場合にどうするか。

例えば、以下のケースクラスで amount の値を 0 以上となるように制限し、0 未満ならインスタンス化を失敗させる事を考えてみます。

case class Quantity(amount: Int)

使用した環境は以下

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

ケースクラスの値を制限

まず、最も単純なのは以下のような実装だと思います。

case class Quantity(amount: Int) {
  if (amount < 0)
    throw new IllegalArgumentException(s"amount($amount) < 0")
}

これだと、例外が throw されてしまい関数プログラミングで扱い難いので Try[Quantity]Option[Quantity] 等を返すようにしたいところです。

そこで、以下のようにケースクラスを abstract 化して、コンパニオンオブジェクトへ生成関数を定義する方法を使ってみました。

sample.scala
import scala.util.{Try, Success, Failure}

sealed abstract case class Quantity private (amount: Int)

object Quantity {
  def apply(amount: Int): Try[Quantity] =
    if (amount >= 0)
      Success(new Quantity(amount){})
    else
      Failure(new IllegalArgumentException(s"amount($amount) < 0"))
}

println(Quantity(1))
println(Quantity(0))
println(Quantity(-1))

// この方法では Quantity へ copy がデフォルト定義されないため
// copy は使えません(error: value copy is not a member of this.Quantity)
//
// println(Quantity(1).map(_.copy(-1)))

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

実行結果
> scala sample.scala

Success(Quantity(1))
Success(Quantity(0))
Failure(java.lang.IllegalArgumentException: amount(-1) < 0)

上記 sample.scala では、以下を直接呼び出せないようにしてケースクラスの勝手なインスタンス化を防止しています。

  • (a) コンストラクタ(new)
  • (b) コンパニオンオブジェクトへデフォルト定義される apply
  • (c) ケースクラスへデフォルト定義される copy

そのために、下記 2点を実施しています。

  • (1) コンストラクタの private 化 : (a) の防止
  • (2) ケースクラスの abstract 化 : (b) (c) の防止

(1) コンストラクタの private 化

以下のように private を付ける事でコンストラクタを private 化できます。

コンストラクタの private 化
case class Quantity private (amount: Int)

これで (a) new Quantity(・・・) の実行を防止できますが、以下のように (b) の apply や (c) の copy を実行できてしまいます。

検証例
scala> case class Quantity private (amount: Int)
defined class Quantity

scala> new Quantity(1)
<console>:14: error: constructor Quantity in class Quantity cannot be accessed in object $iw
       new Quantity(1)

scala> Quantity(1)
res1: Quantity = Quantity(1)

scala> Quantity.apply(2)
res2: Quantity = Quantity(2)

scala> Quantity(3).copy(30)
res3: Quantity = Quantity(30)

(2) ケースクラスの abstract 化

ケースクラスを abstract 化すると、通常ならデフォルト定義されるコンパニオンオブジェクトの apply やケースクラスの copy を防止できるようです。

そのため、(1) と組み合わせることで (a) ~ (c) を防止できます。

ケースクラスの abstract 化とコンストラクタの private 化
sealed abstract case class Quantity private (amount: Int)

以下のように Quantity.apply は定義されなくなります。

検証例
scala> sealed abstract case class Quantity private (amount: Int)
defined class Quantity

scala> new Quantity(1){}
<console>:14: error: constructor Quantity in class Quantity cannot be accessed in <$anon: Quantity>
       new Quantity(1){}
           ^

scala> Quantity.apply(1)
<console>:14: error: value apply is not a member of object Quantity
       Quantity.apply(1)

このままだと何もできなくなるため、実際はコンパニオンオブジェクトへ生成用の関数が必要になります。

sealed abstract case class Quantity private (amount: Int)

object Quantity {
  def create(amount: Int): Quantity = new Quantity(amount){}
}

備考

今回の方法は、以下の書籍に記載されているような ADTs(algebraic data types)と Smart constructors をより安全に定義するために活用できると考えています。

Functional and Reactive Domain Modeling

Functional and Reactive Domain Modeling

quill で DDL を実行

quillScala 用の DB ライブラリで、マクロを使ってコンパイル時に SQL や CQL(Cassandra)を組み立てるのが特徴となっています。

quill には Infix という機能が用意されており、これを使うと FOR UPDATE のような(quillが)未サポートの SQL 構文に対応したり、select 文を直接指定したりできるようですが、CREATE TABLE のような DDL(データ定義言語)の実行は無理そうでした。

そこで、API やソースを調べてみたところ、SQL を直接実行する probeexecuteAction という関数を見つけたので、これを使って CREATE TABLE を実行してみたいと思います。

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

はじめに

今回は Gradle を使ってビルド・実行し、DB には H2 を(インメモリーで)使います。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

mainClassName = 'sample.SampleApp'

repositories {
    jcenter()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.12.6'
    compile 'io.getquill:quill-jdbc_2.12:2.4.2'

    runtime 'com.h2database:h2:1.4.192'
    runtime 'org.slf4j:slf4j-simple:1.8.0-beta2'
}

DB の接続設定は以下のようにしました。

ctx の部分は任意の文字列を用いることができ、H2JdbcContext を new する際の configPrefix 引数で指定します。

src/main/resources/application.conf
ctx.dataSourceClassName=org.h2.jdbcx.JdbcDataSource
ctx.dataSource.url="jdbc:h2:mem:sample"
ctx.dataSource.user=sa

1. probe・executeAction で DDL を実行

それでは、probeexecuteAction をそれぞれ使って CREATE TABLE を実行してみます。

JdbcContext における probe の戻り値は Try[Boolean]executeAction の戻り値は Long となっています。

sample1/src/main/scala/sample/SampleApp.scala
package sample

import io.getquill.{H2JdbcContext, SnakeCase}

case class Item(itemId: String, name: String)
case class Stock(stockId: String, itemId: String, qty: Int)

object SampleApp extends App {
  lazy val ctx = new H2JdbcContext(SnakeCase, "ctx")

  import ctx._

  // probe を使った CREATE TABLE の実行
  val r1 = probe("CREATE TABLE item(item_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10))")
  println(s"create table1: $r1")

  // executeAction を使った CREATE TABLE の実行
  val r2 = executeAction("CREATE TABLE stock(stock_id VARCHAR(10) PRIMARY KEY, item_id VARCHAR(10), qty INT)")
  println(s"create table2: $r2")

  // item への insert
  println( run(query[Item].insert(lift(Item("item1", "A1")))) )
  println( run(query[Item].insert(lift(Item("item2", "B2")))) )

  // stock への insert
  println( run(query[Stock].insert(lift(Stock("stock1", "item1", 5)))) )
  println( run(query[Stock].insert(lift(Stock("stock2", "item2", 3)))) )

  // item の select
  println( run(query[Item]) )
  // stock の select
  println( run(query[Stock]) )

  // Infix を使った select
  val selectStocks = quote(
    infix"""SELECT stock_id AS "_1", name AS "_2", qty AS "_3"
            FROM stock s join item i on i.item_id = s.item_id""".as[Query[(String, String, Int)]]
  )
  println( run(selectStocks) )
}

実行結果は以下の通りで CREATE TABLE に成功しています。 probe の結果は Success(false) で executeAction の結果は 0 となりました。

実行結果
> cd sample1
> gradle run

・・・
[main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
[main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.

create table1: Success(false)
create table2: 0
1
1
1
1
List(Item(item1,A1), Item(item2,B2))
List(Stock(stock1,item1,5), Stock(stock2,item2,3))
List((stock1,A1,5), (stock2,B2,3))

・・・

2. モナドの利用

quill には IO モナドが用意されていたので、これを使って処理を組み立ててみます。

IO は run の代わりに runIO を使う事で取得でき、IO の結果は performIO で取得します。

probe の結果である Try[A]IO.fromTry を使う事で IO にできます。

また、クエリー query[A] では flatMap 等が使えるので for 内包表記で直接合成できましたが(selectStocks の箇所)、query[A].insert(・・・) は flatMap 等を使えなかったので runIO しています。(insertItemAndStock の箇所)

sample2/src/main/scala/sample/SampleApp.scala
package sample

import io.getquill.{H2JdbcContext, SnakeCase}

case class Item(itemId: String, name: String)
case class Stock(stockId: String, itemId: String, qty: Int)

object SampleApp extends App {
  lazy val ctx = new H2JdbcContext(SnakeCase, "ctx")

  import ctx._

  // CREATE TABLE
  val createTables = for {
    it <- probe("CREATE TABLE item(item_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10))")
    st <- probe("CREATE TABLE stock(stock_id VARCHAR(10) PRIMARY KEY, item_id VARCHAR(10), qty INT)")
  } yield (it, st)

  // item と stock へ insert
  val insertItemAndStock = (itemId: String, name: String, stockId: String, qty: Int) => for {
    _ <- runIO( query[Item].insert(lift(Item(itemId, name))) )
    _ <- runIO( query[Stock].insert(lift(Stock(stockId, itemId, qty))) )
  } yield ()

  // stock と item の select(stock と該当する item をタプル化)
  val selectStocks = quote {
    for {
      s <- query[Stock]
      i <- query[Item] if i.itemId == s.itemId
    } yield (i, s)
  }

  // 処理の合成
  val proc = for {
    r1 <- IO.fromTry(createTables)
    _ <- insertItemAndStock("item1", "A1", "stock1", 5)
    _ <- insertItemAndStock("item2", "B2", "stock2", 3)
    r2 <- runIO(selectStocks)
  } yield (r1, r2)

  // 結果
  println( performIO(proc) )
  // トランザクションを適用する場合は以下のようにする
  //println( performIO(proc.transactional) )
}
実行結果
> cd sample2
> gradle run

・・・
[main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
[main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.

((false,false),List((Item(item1,A1),Stock(stock1,item1,5)), (Item(item2,B2),Stock(stock2,item2,3))))

・・・

Akka の FileIO でファイルを読み書き

Akka (akka-stream) の FileIO を使ってファイルの読み書きを行ってみます。

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

はじめに

akka-stream では Java 用の APIakka.stream.javadsl パッケージに、Scala 用の APIakka.stream.scaladsl パッケージに定義されています。

主要なクラスやメソッドは javadsl と scaladsl で概ね共通化されているようですが、Sourcerecover メソッドは deprecated の有無が違っていました。

Source の recover/recoverWith メソッドの deprecated 状況
パッケージ deprecated 有り deprecated 無し 備考
akka.stream.scaladsl recoverWith recover ソースは akka/stream/scaladsl/Flow.scala
akka.stream.javadsl recover recoverWith ソースは akka/stream/javadsl/Source.scala

ここで、deprecated の理由が @deprecated("Use recoverWithRetries instead.", "2.4.4") のようなので、scaladsl の方が正しく、javadsl の方は deprecated するメソッドが違っているのだと思います ※。

 ※ recoverWith の代わりに recoverWithRetries を使えるが
    (引数が 1つ増えただけなので)
    recover は引数の型がそもそも違う

Scala の場合

まずは Scala で実装してみます。

ファイル用の Sink は FileIO.toPath で、Source は FileIO.fromPath で取得できます。

ファイルの入出力の型は akka.util.ByteString となっており、以下では map を使って String との変換を行っています。

Source の recover メソッドを使えば、例外が発生していた場合に代用の値へ差し替えて処理を繋げられます。

今回は、ファイルが存在しない等で IOException が発生した際に、"invalid file, ・・・" という文字列へ差し替えるために recover を使っています。

src/main/scala/SampleApp.scala
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{FileIO, Source}
import akka.util.ByteString

import java.nio.file.Paths
import java.io.IOException

object SampleApp extends App {
  implicit val system = ActorSystem()
  import system.dispatcher  // ExecutionContext を implicit
  implicit val materializer = ActorMaterializer()

  // ファイルへの書き込み(sample1.txt へ "sample data" を出力)
  val res1 = Source.single("sample data")
                   .map(ByteString.fromString)
                   .runWith(FileIO.toPath(Paths.get("sample1.txt")))

  // ファイルの読み込み(sample2.txt の内容を println)
  val res2 = FileIO.fromPath(Paths.get("sample2.txt"))
                   .map(_.utf8String)
                   .recover {
                     case e: IOException => s"invalid file, ${e}"
                   }
                   .runForeach(println)

  res1.flatMap(_ => res2).onComplete(_ => system.terminate)
}

ビルドと実行

Gradle のビルド定義ファイルは以下の通りです。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

mainClassName = 'SampleApp'

repositories {
    jcenter()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.12.1'
    compile 'com.typesafe.akka:akka-stream_2.12:2.5-M1'
}

sample2.txt ファイルの無い状態で実行します。

実行結果1
> gradle -q run

invalid file, java.nio.file.NoSuchFileException: sample2.txt

sample2.txt を作成して実行します。

実行結果2
> echo %time% > sample2.txt

> gradle -q run

22:11:48.03

Java の場合

次に Java で実装してみます。

Scala と概ね同じですが、akka.stream.javadsl.Source でも recover の引数が scala.PartialFunction となっており、多少の工夫が必要です。

recover の引数に合う scala.PartialFunction は Akka の akka.japi.pf.PFBuilder で作れるので、以下では Match.match から PFBuilder を取得して使っています。

なお、Akka では scala.PartialFunction を組み立てるための Java 用の APIakka.japi.pf パッケージにいくつか用意されています。

src/main/java/SampleApp.java
import akka.actor.ActorSystem;

import akka.japi.pf.Match;
import akka.japi.pf.PFBuilder;

import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.FileIO;
import akka.stream.javadsl.Source;

import akka.util.ByteString;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;

public class SampleApp {
    public static void main(String... args) {
        final ActorSystem system = ActorSystem.create();
        final Materializer materializer = ActorMaterializer.create(system);

        // ファイルへの書き込み(sample1.txt へ "sample data" を出力)
        CompletableFuture<?> res1 = Source.single("sample data")
                .map(ByteString::fromString)
                .runWith(FileIO.toPath(Paths.get("sample1.txt")), materializer)
                .toCompletableFuture();

        // scala.PartialFunction のビルダー定義
        PFBuilder<Throwable, String> pfunc = 
            Match.match(IOException.class, e -> "invalid file, " + e);

        // ファイルの読み込み(sample2.txt の内容を println)
        CompletableFuture<?> res2 = FileIO.fromPath(Paths.get("sample2.txt"))
                .map(ByteString::utf8String)
                .recover(pfunc.build())
                .runForeach(System.out::println, materializer)
                .toCompletableFuture();

        CompletableFuture.allOf(res1, res2)
                .handle((v, e) -> system.terminate())
                .join();
    }
}

ビルドと実行

Gradle のビルド定義ファイルは以下の通りです。

build.gradle
apply plugin: 'application'

mainClassName = 'SampleApp'

repositories {
    jcenter()
}

dependencies {
    compile 'com.typesafe.akka:akka-stream_2.12:2.5-M1'
}

sample2.txt ファイルの無い状態で実行します。

実行結果1
> gradle -q run

・・・\src\main\java\SampleApp.javaは非推奨のAPIを使用またはオーバーライドしています。
・・・
invalid file, java.nio.file.NoSuchFileException: sample2.txt

recover が deprecated されている件で警告メッセージが出力されますが、上述したように recover を deprecated しているのが誤りだと思われるので無視します。

sample2.txt を作成して実行します。

実行結果2
> echo %time% > sample2.txt

> gradle -q run

・・・
22:14:16.00

Gradle で ScalaPB を使う

前回と同様の処理を ScalaPB で行ってみました。

ScalaPB であればビルドツールに sbt を使う方が簡単かもしれませんが、引き続き Gradle を使います。

今回作成したソースは http://github.com/fits/try_samples/tree/master/blog/20160905/

proto ファイル

前回と同じファイルですが、ファイル名に - を含むと都合が悪いようなので ※ ファイル名だけ変えています。

※ ScalaPB 0.5.40 では、デフォルトで proto ファイル名が
   そのままパッケージ名の一部となりました
   (パッケージ名は <java_package オプションの値>.<protoファイル名> )

ちなみに、java_outer_classname のオプション設定は無視されるようです。

proto/addressbook.proto (.proto ファイル)
syntax = "proto3";

package sample;

option java_package = "sample.model";
option java_outer_classname = "AddressBookProtos";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}

Gradle ビルド定義

基本的な構成は前回と同じですが、ScalaPBC を実行してソースを生成する等、Scala 用に変えています。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

// protoc によるソースの自動生成先
def protoDestDir = 'src/main/protoc-generated'
// proto ファイル名
def protoFile = 'proto/addressbook.proto'

mainClassName = 'SampleApp'

repositories {
    jcenter()
}

configurations {
    scalapbc
}

dependencies {
    scalapbc 'com.trueaccord.scalapb:scalapbc_2.11:0.5.40'

    compile 'org.scala-lang:scala-library:2.11.8'
    compile 'com.trueaccord.scalapb:scalapb-runtime_2.11:0.5.40'
}

task scalapbc << {
    mkdir(protoDestDir)

    javaexec {
        main = 'com.trueaccord.scalapb.ScalaPBC'
        classpath = configurations.scalapbc
        args = [ protoFile, "--scala_out=${protoDestDir}" ]
    }
}

compileScala {
    dependsOn scalapbc
    source protoDestDir
}

clean {
    delete protoDestDir
}

サンプルアプリケーション

こちらも前回と同じ処理内容ですが、ScalaPB 用の実装となっています。

src/main/scala/SampleApp.scala
import java.io.ByteArrayOutputStream

import sample.model.addressbook.Person
import Person.PhoneNumber
import Person.PhoneType._

object SampleApp extends App {

    val phone = PhoneNumber("000-1234-5678", HOME)
    val person = Person(name = "sample1", phone = Seq(phone))

    println(person)

    val output = new ByteArrayOutputStream()

    try {
        person.writeTo(output)

        println("----------")

        val restoredPerson = Person.parseFrom(output.toByteArray)

        println(restoredPerson)

    } finally {
        output.close
    }
}

ビルドと実行

ビルドと実行の結果は以下の通りです。

前回と違って今回のビルド(scalapbc の実施)には python コマンドが必要でした。※

※ python コマンドを呼び出せるように環境変数 PATH 等を設定しておきます
   今回は Python 2.7 を使用しました
実行結果
> gradle run

・・・
:scalapbc
protoc-jar: protoc version: 300, detected platform: windows 10/amd64
protoc-jar: executing: [・・・\Temp\protoc8428481850206377506.exe, --plugin=protoc-gen-scala=・・・\Temp\protocbridge9000836851429371052.bat, proto/addressbook.proto, --scala_out=src/main/protoc-generated]
:compileScala
・・・
:run
name: "sample1"
phone {
  number: "000-1234-5678"
  type: HOME
}

----------
name: "sample1"
phone {
  number: "000-1234-5678"
  type: HOME
}


BUILD SUCCESSFUL

scalapbc タスクの実行によって以下のようなソースが生成されました。

  • src/main/protoc-generated/sample/model/addressbook/AddressBook.scala
  • src/main/protoc-generated/sample/model/addressbook/AddressbookProto.scala
  • src/main/protoc-generated/sample/model/addressbook/Person.scala

java_package オプションの設定値は反映されていますが、java_outer_classname オプション設定の方は無視されているようです。

Gradle と Querydsl Scala を使った Querydsl SQL のコード生成

前回JPA に続き、今回は Gradle と Querydsl Scala を使って Querydsl SQL のコード生成を試します。

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

はじめに

Querydsl SQL の場合は ScalaJava と同じ要領でコードを生成します。 (Java の場合は 「Gradle を使った Querydsl SQL のコード生成」 参照)

ただし、MetaDataExporterScala 用の serializerClass 等を設定する必要があります。

Gradle ビルド定義

Gradle 用のビルド定義ファイルは以下のようになります。

build.gradle
apply plugin: 'scala'

// Querydsl のソース生成先パッケージ名
ext.modelPackage = 'sample.model'
// Querydsl のソース生成先ディレクトリ
ext.qdslDestDir = 'src/main/qdsl-generated'
// DB接続 URL
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.querydsl:querydsl-sql-codegen:4.0.3'
        classpath 'com.querydsl:querydsl-scala:4.0.3'
        classpath 'org.scala-lang:scala-library:2.11.7'
        // MySQL へ接続してコード生成する場合
        classpath 'mysql:mysql-connector-java:5.1.36'
    }
}

repositories {
    jcenter()
}

dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'
}
// コード生成
task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    // Bean のコードも生成する場合は以下を有効化
    //exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}

compileScala {
    dependsOn generate

    sourceSets.main.scala.srcDir qdslDestDir
}

clean {
    delete qdslDestDir
}

DB は MySQL を使用し、「JPA における一対多のリレーションシップ - EclipseLink」 で使ったものと同じテーブルを使用します。

使用する DB のテーブル定義 (DDL
CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `price` decimal(10,0) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `product_variation` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(20) NOT NULL DEFAULT 0,
  `color` varchar(10) NOT NULL,
  `size` varchar(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

サンプルアプリケーション1

それでは簡単なサンプルアプリケーションを作成し実行してみます。

ビルド定義

先程の build.gradle へ少しだけ手を加え、サンプルアプリケーション sample.SampleApp を実行するようにしました。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

ext.modelPackage = 'sample.model'
ext.qdslDestDir = 'src/main/qdsl-generated'
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

mainClassName = 'sample.SampleApp'

buildscript {
    ・・・
}
・・・
dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'

    runtime 'mysql:mysql-connector-java:5.1.36'
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}

task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    //exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}
・・・

generate タスクを実行すると以下のファイルが生成されます。

  • src/main/qdsl-generated/sample/model/QProduct.scala
  • src/main/qdsl-generated/sample/model/QProductVariation.scala

実行クラス

単純な insert・select 処理を実装しました。

src/main/scala/sample/SampleApp.scala
package sample

import com.querydsl.sql.dml.SQLInsertClause
import com.querydsl.sql.{SQLQuery, MySQLTemplates}

import java.util.Properties
import java.sql.DriverManager

import scala.collection.JavaConversions._

import sample.model.{QProduct, QProductVariation}

object SampleApp extends App {
    val conf = new Properties()
    conf.load(getClass.getClassLoader.getResourceAsStream("db.properties"))

    val con = DriverManager.getConnection(conf.getProperty("url"), conf)
    con.setAutoCommit(false)

    val templates = new MySQLTemplates()

    val p = QProduct as "p"
    val v = QProductVariation as "v"

    // product へ insert
    val pid: Long = new SQLInsertClause(con, templates, p)
        .set(p.name, s"sample${System.currentTimeMillis()}")
        .set(p.price, 1500L)
        .executeWithKey(p.id)

    // product_variation へ insert
    new SQLInsertClause(con, templates, v)
        .set(v.productId, pid).set(v.color, "Green").set(v.size, "L").addBatch()
        .set(v.productId, pid).set(v.color, "Blue").set(v.size, "S").addBatch()
        .execute()

    con.commit()

    val query = new SQLQuery(con, templates)

    // product と product_variation を join して select
    val res = query.from(p)
        .join(v).on(v.productId.eq(p.id))
        .where(p.price.between(1300, 2500))
        .select(p.id, p.name, p.price, v.color, v.size)
        .fetch()

    // id, name, price でグルーピング
    val groupedRes = res.groupBy(x => (x.get(p.id), x.get(p.name), x.get(p.price)))

    println(groupedRes)

    con.close()
}

DB 接続設定ファイル

DB の接続設定に以下のプロパティファイルを使用します。

src/main/resources/db.properties
url=jdbc:mysql://localhost:3306/jpa_sample?characterEncoding=utf8
user=root
password=

実行

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

実行結果
> gradle run

:compileJava UP-TO-DATE
:generate
:compileScala
:processResources
:classes
:run

Map((3,sample1439089472290,1500) -> ArrayBuffer([3, sample1439089472290, 1500, Green, L], [3, sample1439089472290, 1500, Blue, S]))

サンプルアプリケーション2

次は Bean を使ったサンプルです。

ビルド定義

exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer を有効化し、Bean のコード生成を行うようにしました。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

ext.modelPackage = 'sample.model'
ext.qdslDestDir = 'src/main/qdsl-generated'
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

mainClassName = 'sample.SampleApp2'

buildscript {
    ・・・
}
・・・
dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'
    compile 'org.apache.commons:commons-dbcp2:2.1.1'

    runtime 'mysql:mysql-connector-java:5.1.36'
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}

task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    // Bean のコード生成を有効化
    exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}
・・・

generate タスクを実行すると以下のファイルが生成されます。

  • src/main/qdsl-generated/sample/model/Product.scala
  • src/main/qdsl-generated/sample/model/ProductVariation.scala
  • src/main/qdsl-generated/sample/model/QProduct.scala
  • src/main/qdsl-generated/sample/model/QProductVariation.scala

実行クラス

処理内容は、サンプルアプリケーション1 と同じですが、com.querydsl.scala.sql.SQL トレイトを使って Connection を直接扱わなくても済むようにしています。

SQL トレイトの tx メソッドへ DB 処理 (insert や select 等) を渡します。 tx では大まかに以下のような処理を実行するようです。

  • (1) DataSource から Connection 取得 (setAutoCommit を false へ設定)
  • (2) 引数で渡した処理の実行
  • (3) コミット or ロールバック
  • (4) Connectionclose

なお、Bean を使って insert する場合は populate メソッドを使います。

src/main/scala/sample/SampleApp2.scala
package sample

import com.querydsl.scala.sql.SQL
import com.querydsl.sql.{SQLTemplates, MySQLTemplates}

import org.apache.commons.dbcp2.BasicDataSourceFactory

import java.util.Properties
import javax.sql.DataSource

import scala.collection.JavaConversions._

import sample.model.{Product, ProductVariation, QProduct, QProductVariation}

// com.querydsl.scala.sql.SQL トレイトの実装
case class QueryDSLHelper(dataSource: DataSource, templates: SQLTemplates) extends SQL

object SampleApp2 extends App {
    val product = (name: String, price: Long) => {
        val res = new Product()
        res.name = name
        res.price = price
        res
    }

    val variation = (productId: Long, color: String, size: String) => {
        val res = new ProductVariation()
        res.productId = productId
        res.color = color
        res.size = size
        res
    }

    val conf = new Properties()
    conf.load(getClass.getClassLoader.getResourceAsStream("db.properties"))

    val dataSource = BasicDataSourceFactory.createDataSource(conf)
    val qdsl = QueryDSLHelper(dataSource, new MySQLTemplates())

    val p = QProduct as "p"
    val v = QProductVariation as "v"

    qdsl.tx {
        // product へ insert
        val pid = qdsl.insert(p)
            .populate(product(s"test${System.currentTimeMillis()}", 2000L))
            .executeWithKey(p.id)

        // product_variation へ insert
        qdsl.insert(v)
            .populate(variation(pid, "Red", "M")).addBatch()
            .populate(variation(pid, "Yellow", "F")).addBatch()
            .execute()
    }

    qdsl.tx {
        // product と product_variation を join して select
        val res = qdsl.from(p)
            .join(v).on(v.productId.eq(p.id))
            .where(p.price.between(1300, 2500))
            .select(p.id, p.name, p.price, v.color, v.size)
            .fetch()

        // id, name, price でグルーピング
        val groupedRes = res.groupBy(x => (x.get(p.id), x.get(p.name), x.get(p.price)))

        println(groupedRes)
    }
}

DB 接続設定ファイル

commons-dbcp2 用のプロパティファイルを使いました。

src/main/resources/db.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jpa_sample?characterEncoding=utf8
username=root
password=

実行

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

実行結果
> gradle run

:compileJava UP-TO-DATE
:generate
:compileScala
:processResources
:classes
:run

Map((3,sample1439089472290,1500) -> ArrayBuffer([3, sample1439089472290, 1500, Green, L], [3, sample1439089472290, 1500, Blue, S]), (4,test1439089637936,2000) -> ArrayBuffer([4, test1439089637936, 2000, Red, M], [4, test1439089637936, 2000, Yellow, F]))

Gradle と Querydsl Scala を使った Querydsl JPA のコード生成

Gradle と Querydsl Scala を使って Querydsl JPAScala 用コード生成を試してみました。

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

はじめに

Gradle を使った Querydsl JPA のコード生成」 ではアノテーションプロセッサを使って Querydsl JPA のコードを生成しましたが、Scala の場合は com.querydsl.codegen.GenericExporter クラスを使うようです。

GenericExporter でコード生成するには JPA のエンティティクラスをロードできなければなりません。 (つまり、エンティティクラスを事前にコンパイルしておく必要あり)

Gradle ビルド定義

エンティティクラスを Querydsl のコード生成前にコンパイルするため、今回は以下のようにエンティティクラスだけをコンパイルするタスク modelCompile と Querydsl 用のコードを生成するタスク generate を追加しました。

番号 概要 タスク名
(1) エンティティクラスをコンパイル modelCompile
(2) (1) のエンティティクラスを使って Querydsl JPAScala 用コードを生成 generate
(3) (2) で生成したソースをビルド compileScala

(1) では src/main/scala-model へ配置したエンティティクラスのソース (Scala) をビルドして build/classes/main へ出力します。

(2) では com.querydsl.codegen.GenericExporter を使って Scala 用の Querydsl JPA コードを src/main/qdsl-generated へ生成します。

(3) で (2) の生成したソースをビルドできるように sourceSets.main.scala.srcDirsrc/main/qdsl-generated を追加しています。

なお、(2) で (1) のクラスをロードできるように buildscriptclasspathbuild/classes/main を追加しているのですが、これが原因で初回実行時や clean 直後は (1) と (2) を別々に実行する必要があります。

これは、build/classes/main へクラスファイルが配置されていない状態 ((1) の実施前) で Gradle を実行すると given scan urls are empty. set urls in the configuration とメッセージが出力され、以降のタスクで build/classes/main をクラスパスとして認識しない事が原因です。

build.gradle
apply plugin: 'scala'

// スキャン対象の JPA エンティティクラスのパッケージ名
ext.modelPackage = 'sample.model'
// JPA エンティティクラスのソースディレクトリ
ext.modelSourceDir = 'src/main/scala-model'
// Querydsl のソース生成先ディレクトリ
ext.qdslDestDir = 'src/main/qdsl-generated'

buildscript {
    // JPA エンティティクラスのビルド結果の出力先ディレクトリ
    // buildscript の classpath へ設定する必要があるため、ここで定義している
    ext.destDir = "$buildDir/classes/main"

    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.querydsl:querydsl-codegen:4.0.2'
        classpath 'com.querydsl:querydsl-scala:4.0.2'

        classpath 'org.scala-lang:scala-library:2.11.7'

        classpath 'javax:javaee-api:7.0'
        // コード生成時に JPA エンティティクラスをロードさせるための設定
        classpath files(destDir)
    }
}

repositories {
    jcenter()
}

dependencies {
    compile 'com.querydsl:querydsl-jpa:4.0.2'
    compile 'com.querydsl:querydsl-scala:4.0.2'

    compile 'org.scala-lang:scala-library:2.11.7'

    compile 'org.apache.commons:commons-dbcp2:2.1'
    compile 'javax:javaee-api:7.0'
}

// (1) JPA エンティティクラスをコンパイル
task modelCompile(type: ScalaCompile) {
    // ソースディレクトリ
    source = modelSourceDir
    // クラスパスの設定 (buildscript のクラスパスを設定)
    classpath = buildscript.configurations.classpath
    // クラスファイルの出力先
    destinationDir = file(destDir)

    // 以下が必須 (ファイル名やパスは何でも良さそう)
    scalaCompileOptions.incrementalOptions.analysisFile = file("${buildDir}/tmp/scala/compilerAnalysis/compileCustomScala.analysis")
}

// (2) Querydsl JPA の Scala 用コードを生成
task generate(dependsOn: 'modelCompile') {
    def exporter = new com.querydsl.codegen.GenericExporter()
    // コード生成先ディレクトリの設定
    exporter.targetFolder = file(qdslDestDir)

    exporter.serializerClass = com.querydsl.scala.ScalaEntitySerializer
    exporter.typeMappingsClass = com.querydsl.scala.ScalaTypeMappings

    exporter.entityAnnotation = javax.persistence.Entity
    exporter.embeddableAnnotation = javax.persistence.Embeddable
    exporter.embeddedAnnotation = javax.persistence.Embedded
    exporter.skipAnnotation = javax.persistence.Transient
    exporter.supertypeAnnotation = javax.persistence.MappedSuperclass
    // Scala ソースの出力
    exporter.createScalaSources = true
    // コード生成の実施
    exporter.export(modelPackage)
}

// (3) ソースをビルド
compileScala {
    // generate タスクとの依存設定
    dependsOn generate
    // Querydsl のコード生成先ディレクトリを追加
    sourceSets.main.scala.srcDir qdslDestDir
}

clean {
    delete qdslDestDir
}

サンプルアプリケーション

それでは簡単なサンプルアプリケーションを作成し実行してみます。

ビルド定義

先程の build.gradle へ少しだけ手を加え、EclipseLink と MySQL を使ったサンプルアプリケーション sample.SampleApp を実行するようにしました。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

ext.modelPackage = 'sample.model'
ext.modelSourceDir = 'src/main/scala-model'
ext.qdslDestDir = 'src/main/qdsl-generated'
// 実行クラス
mainClassName = 'sample.SampleApp'

buildscript {
    ・・・
}
・・・
dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.2'
    compile 'com.querydsl:querydsl-jpa:4.0.2'
    compile 'org.scala-lang:scala-library:2.11.7'
    compile 'org.apache.commons:commons-dbcp2:2.1'
    compile 'javax:javaee-api:7.0'

    // 実行用の依存ライブラリ
    runtime 'org.eclipse.persistence:eclipselink:2.6.1-RC1'
    runtime 'mysql:mysql-connector-java:5.1.36'
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}
・・・

JPA エンティティクラス

JPA における一対多のリレーションシップ - EclipseLink」 で使った JPA エンティティクラスを Scala で実装し直しました。

src/main/scala-model/sample/model/Product.scala
package sample.model

import javax.persistence._

import java.util.ArrayList
import java.util.List
import java.math.BigDecimal

@Entity
class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = _
    var name: String = _
    var price: BigDecimal = _

    @OneToMany(fetch = FetchType.EAGER, cascade= Array(CascadeType.ALL))
    @JoinColumn(name = "product_id")
    val variationList: List[ProductVariation] = new ArrayList()

    override def toString = s"Product(id: ${id}, name: ${name}, price: ${price}, variationList: ${variationList})"
}
src/main/scala-model/sample/model/ProductVariation.scala
package sample.model

import javax.persistence._

@Entity
@Table(name = "product_variation")
class ProductVariation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = _
    var color: String = _
    var size: String = _

    override def toString = s"ProductVariation(id: ${id}, color: ${color}, size: ${size})"
}

実行クラス

Querydsl JPA を使った単純な検索処理を行います。

src/main/scala/sample/SampleApp.scala
package sample

import sample.model.Product
import sample.model.ProductVariation
import sample.model.QProduct

import com.querydsl.jpa.impl.JPAQuery

import javax.persistence.Persistence
import java.math.BigDecimal

import scala.collection.JavaConversions._

object SampleApp extends App{
    def product(name: String, price: BigDecimal, variationList: ProductVariation*) = {
        val res = new Product()
        res.name = name
        res.price = price
        variationList.foreach(res.variationList.add)
        res
    }

    def variation(color: String, size: String) = {
        val res = new ProductVariation()
        res.color = color
        res.size = size
        res
    }

    val emf = Persistence.createEntityManagerFactory("jpa")
    val em = emf.createEntityManager()

    val tx = em.getTransaction()
    tx.begin()

    val p1 = product(
        "sample" + System.currentTimeMillis(), 
        new BigDecimal(1250),
        variation("White", "L"),
        variation("Black", "M")
    )

    em.persist(p1)

    tx.commit()

    val p = QProduct as "p"

    val query = new JPAQuery[Product](em)

    // Querydsl JPA による検索
    val res = query.from(p).where(p.name.startsWith("sample")).fetch()
    // 結果の出力
    res.foreach(println)

    em.close()
}

実行

JPA における一対多のリレーションシップ - EclipseLink」 で使った DB や JPA 設定ファイルを使って実行します。

src/main/resources/META-INF/persistence.xml
<persistence
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">

    <persistence-unit name="jpa">
        <class>sample.model.Product</class>
        <class>sample.model.ProductVariation</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa_sample" />
            <property name="javax.persistence.jdbc.user" value="root" />

            <property name="eclipselink.logging.level" value="FINE" />
        </properties>
    </persistence-unit>
</persistence>

初回実行時や clean 直後は、modelCompilegenerate 以降のタスクを分けて実行する必要があります。 (上の方でも書きましたが buildscriptclasspathbuild/classes/main を設定している事が原因です)

エンティティクラスのコンパイル (modelCompile タスクの実行)
> gradle modelCompile

given scan urls are empty. set urls in the configuration
:modelCompile

以下のファイルが生成されます。

  • src/main/qdsl-generated/sample/model/QProduct.scala
  • src/main/qdsl-generated/sample/model/QProductVariation.scala
実行結果 (run タスクの実行)
> gradle run

:compileJava UP-TO-DATE
:modelCompile
:generate
:compileScala
:processResources
:classes
:run
・・・
Product(id: 3, name: sample1, price: 100, variationList: [ProductVariation(id: 4, color: Black, size: M), ProductVariation(id: 5, color: White, size: L)])
Product(id: 4, name: sample1437821487341, price: 1250, variationList: [ProductVariation(id: 6, color: White, size: L), ProductVariation(id: 7, color: Black, size: M)])

Apache Spark でロジスティック回帰

以前 ※ に R や Julia で試したロジスティック回帰を Apache Spark の MLlib (Machine Learning Library) を使って実施してみました。

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

※「 R でロジスティック回帰 - glm, MCMCpack 」、「 Julia でロジスティック回帰-glm

はじめに

R の時と同じデータを使いますが、ヘッダー行を削除しています。(「R でロジスティック回帰 - glm, MCMCpack」 参照)

データ data4a.csv
8,1,9.76,C
8,6,10.48,C
8,5,10.83,C
・・・

データ内容は以下の通り。個体 i それぞれにおいて 「 { N_i } 個の観察種子のうち生きていて発芽能力があるものは { y_i } 個」 となっています。

項目 内容
N 観察種子数
y 生存種子数
x 植物の体サイズ
f 施肥処理 (C: 肥料なし, T: 肥料あり)

体サイズ x と肥料による施肥処理 f が種子の生存する確率(ある個体 i から得られた種子が生存している確率)にどのように影響しているかをロジスティック回帰で解析します。

MLlib によるロジスティック回帰

今回は org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS を使用します。

LogisticRegressionWithLBFGS について

LogisticRegressionWithLBFGS で以前と同様のロジスティック回帰を実施するには以下が必要です。

  • setIntercept で true を設定

この値が false (デフォルト値) の場合、結果の intercept 値が 0 になります。

なお、今回のように二項分布を使う場合は numClasses の値を変更する必要はありませんが (デフォルト値が 2 のため)、応答変数が 3状態以上の多項分布を使う場合は setNumClasses で状態数に応じた値を設定します。

LabeledPoint について

LogisticRegressionWithLBFGS へ与えるデータは LabeledPoint で用意します。

R や Julia では 応答変数 ~ 説明変数1 + 説明変数2 + ・・・ のように応答変数と説明変数を指定しましたが、LabeledPoint では下記のようにメンバー変数で表現します。

メンバー変数 応答変数・説明変数
label 応答変数
features 説明変数

値は Double とする必要がありますので、f 項目のような文字列値は数値化します。

更に、二項分布を使う場合 (numClasses = 2) は応答変数の値が 0 か 1 でなければなりません。

LabeledPoint への変換例

例えば、以下のようなデータを応答変数 y 項目、説明変数 x と f 項目の LabeledPoint へ変換する場合

変換前のデータ (N = 8, y = 6)
8,6,10.48,C

次のようになります。

変換後のデータイメージ
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 1.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 0.0, features: Vector(10.48, 0.0))
LabeledPoint(label: 0.0, features: Vector(10.48, 0.0))

8個(N)の中で 6個(y)生存していたデータのため、 label (応答変数) の値が 1.0 (生存) のデータ 6個と 0.0 のデータ 2個へ変換します。

ちなみに、f 項目の値が C の場合は 0.0、T の場合は 1.0 としています。

実装

実装してみると以下のようになります。

LogisticRegression.scala
import org.apache.spark.SparkContext
import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.linalg.Vectors

object LogisticRegression extends App {
    // f項目の値を数値へ変換
    val factor = (s: String) => s match {
        case "C" => 0
        case _ => 1
    }

    val sc = new SparkContext("local", "LogisticRegression")

    // データの準備 (100行のデータ -> 800個の LabeledPoint)
    val rdd = sc.textFile(args(0)).map(_.split(",")).flatMap { d =>
        val n = d(0).toInt
        val x = d(1).toInt
        // 説明変数の値
        val v = Vectors.dense(d(2).toDouble, factor(d(3)))

        // 応答変数が 1 のデータ x 個と 0 のデータ n - x 個を作成
        List.fill(x)( LabeledPoint(1, v) ) ++ 
            List.fill(n -x)( LabeledPoint(0, v) )
    }

    // ロジスティック回帰の実行
    val res = new LogisticRegressionWithLBFGS()
//      .setNumClasses(2) //省略可
        .setIntercept(true)
        .run(rdd)

    println(res)
}

ビルド

以下のような Gradle ビルド定義ファイルを使って実行します。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

mainClassName = 'LogisticRegression'

repositories {
    jcenter()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.11.6'

    compile('org.apache.spark:spark-mllib_2.11:1.3.1') {
        // ログ出力の抑制
        exclude module: 'slf4j-log4j12'
    }

    // ログ出力の抑制
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}

run {
    if (project.hasProperty('args')) {
        args project.args.split(' ')
    }
}

不要な WARN ログ出力を抑制するため以下のファイルも用意しました。

src/main/resources/log4j.properties
log4j.rootLogger=off

実行

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

実行結果
> gradle run -Pargs=data4a.csv

:clean
:compileJava UP-TO-DATE
:compileScala
:processResources
:classes
:run
(weights=[1.952347703282676,2.021401680901667], intercept=-19.535421113192506)

BUILD SUCCESSFUL

以前に実施した R の結果 (Estimate の値) とほとんど同じ値になっています。

R の glm 関数による結果
Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -19.5361     1.4138  -13.82   <2e-16 ***
x             1.9524     0.1389   14.06   <2e-16 ***
fT            2.0215     0.2313    8.74   <2e-16 ***