S-JIS[2011-03-13] 変更履歴

Asakusa Scala DSL改造案

@asami224さんのAsakusa Scala DSLの改造案(になったらいいな的な雰囲気のもの)を出してみる。

Asakusa Frameworkは、Hadoopを扱いやすくする為のフレームワーク。
AsakusaFW用の記述を行うのがAsakusa DSL。本家はJavaで書くようになっているが、それのScala版が開発されようとしている。


概要

@asami224さんのAsakusa Scala DSLは、 データソースをクラス(型)で定義し、演算子クラスは型パラメーターで指定する。

class 仕入明細データ extends DataSource
class 仕入返品データ extends DataSource
class 費用振替データ extends DataSource
class 売価変更データ extends DataSource

class 仕入データ extends DataSource4[仕入明細データ, 仕入返品データ, 費用振替データ, 売価変更データ]
case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends
  Operation12[仕入データ, 仕入データTRN, 売価変更在庫変更TRN](cout)

このOperation12というクラスは、入力データが1個、出力データが2個であることを示している。
この例では、型パラメーターの最初の1個(仕入データ)が入力で、次の2個(仕入データTRN・売価変更在庫変更TRN)が出力。
Operation21というクラスならば、最初の2個が入力で、最後の1個が出力を意味することになる。

この辺り、Scalaのメソッドなら引数リストを分割できるので、入力と出力それぞれで引数リストを分ければ分かりやすくなるんじゃないかなーと思ったので試してみた。
メソッドならオーバーロードも出来るので、メソッド名に「12」のような数字を付ける必要も無いし。


ちなみに型パラメーターは引数リストの様な分割は出来ないんだよね。

case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends
  Operation12[仕入データ][仕入データTRN, 売価変更在庫変更TRN](cout)

ん? 入力トレイトと出力トレイトに分けたらいいのかも?

case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends
  OperationIn1[仕入れデータ] with OperationOut2[仕入データTRN, 売価変更在庫変更TRN](cout)

表現の例

作ってみたライブラリーは後述するとして、どのように表現できたかを@asami224さんのサンプルと比較してみる。

データソースの定義 asamiさん版
class 仕入明細データ extends DataSource
データソースをクラスでなくシングルトンオブジェクトで定義する。
ひしだま版
object 仕入明細データ extends DataSource
複数データソース asamiさん版
class 仕入データ extends
  DataSource4[仕入明細データ, 仕入返品データ,
              費用振替データ, 売価変更データ]
asamiさんのは型パラメーターの個数に応じてDataSourceNというクラスを使う。
自分のはDataSourcesという1つのクラスのみで、
型パラメーターの代わりにシングルトンオブジェクト(インスタンス)を指定する。
基本コンストラクターでデフォルト引数を指定しているので、個数はいくつでもOK。
ひしだま版
object 仕入データ extends
  DataSources(仕入明細データ, 仕入返品データ,
              用振替データ, 売価変更データ)
@frsyukiさん版
val 仕入データ = 仕入明細データ + 仕入返品データ +
                 費用振替データ + 売価変更データ
frsyukiさんのと同じようにこちらもインスタンスなので
+演算子を定義することは出来るけれども、
型がちゃんと残せなかったので、断念。
演算子 asamiさん版
case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends
  Operation12[仕入データ, 仕入データTRN, 売価変更在庫変更TRN](cout)
Operationもクラス名の数字を無くした。
コンストラクターの引数リストも分けた。
元がケースクラスなのでケースオブジェクトにしたが、caseは要るのかな?
あと、Portもとりあえず削った。
(自分の範囲では使わない、というか使い方が分からなかったので(爆))
ひしだま版
case object 仕入データ取り込み extends
  Operation(仕入データ)(仕入データTRN, 売価変更在庫変更TRN)
フロー asamiさん版
class 会計処理バッチ extends Flow32[仕入データ, 修正データ, 請求TRN,
                                    会計データTRN, 売価変更在庫変更TRN] {
  start proc12(仕入データ取り込み(out2))
        proc21(残高更新(in2))
        proc21(照合処理(in3)) end
}
Flowクラスについては、引数リストを2つに分けて
クラス名からも数字が取れた。
しかしprocメソッドの方は数字が残っている。
というのは、引数が同一で型パラメーターだけ異なるというオーバーロードは出来ないようなので…残念。

※start〜endは実際には1行でないとコンパイルエラーになる
ひしだま版1
object 会計処理バッチ extends Flow(仕入データ, 修正データ, 請求TRN)(
                                   会計データTRN, 売価変更在庫変更TRN) {
  start proc12(仕入データ取り込み()(out2))
        proc21(残高更新(in2)())
        proc21(照合処理(in3)()) end
}
ひしだま版2
class 仕入データ取り込み extends
  Operation(仕入データ)(仕入データTRN, 売価変更在庫変更TRN)
object 会計処理バッチ extends Flow(仕入データ, 修正データ, 請求TRN)(
                                   会計データTRN, 売価変更在庫変更TRN) {
  start
  val 仕入データ取り込み = new 仕入データ取り込み {
//  in1 = 会計処理バッチ.this.in1
    in1 = flow.in1
    out2 = flow.out2
  }
  val 残高更新 = new 残高更新 {
    in1 = 仕入データ取り込み.out1
    in2 = flow.in2
  }
  val 照合処理 = new 照合処理 {
    in1 = 残高更新.out1
    in2 = flow.in3
  }
  end(照合処理.out1)
}
procメソッドを無くしてみたバージョン。
演算子はオブジェクトでなくクラスにして、
フロー定義内でインスタンスと変数を作る。
クラス名と変数名で同じものが出てくるので、1つにまとめたい気はするけど(苦笑)

この方式の欠点は、in2やout2の定義漏れが検出できないことかな。
ひしだま版3
object 会計処理バッチ extends Flow(仕入データ, 修正データ, 請求TRN)(
                                   会計データTRN, 売価変更在庫変更TRN) {
  start {
    val 仕入データ取り込みflow = 仕入データ取り込み(flow.in1)
    仕入データ取り込みflow.out2 = flow.out2

    val 残高更新flow = 残高更新(仕入データ取り込みflow.out1, flow.in2)

    val 照合処理flow = 照合処理(残高更新flow.out1, flow.in3)
    照合処理flow.out1 = flow.out1
  }
}
演算子はオブジェクトに戻して、apply()メソッドを使うことにしたバージョン。
この場合、変数名とオブジェクト名を同じにすると区別がつかないので、
別の名前を付けなければならない。

フローの例(全体)

object 仕入明細データ extends DataSource
object 仕入返品データ extends DataSource
object 費用振替データ extends DataSource
object 売価変更データ extends DataSource

object 修正在庫振替TRN extends DataSource
object 修正未収収益TRN extends DataSource
object 修正在庫移動TRN extends DataSource
object 未払計上TRN extends DataSource

object 仕入TRN extends DataSource
object 在庫振替TRN extends DataSource
object 在庫移動TRN extends DataSource
object 未収収益TRN extends DataSource

object 計上済仕入TRN extends DataSource
object 計上済未収収益TRN extends DataSource
object 計上済未払費用TRN extends DataSource
object 更新済買掛残高TRN extends DataSource

object 請求エラーTRN extends DataSource
object 支払不可消込TRN extends DataSource
object 支払可消込TRN extends DataSource
object 照合済支払費用TRN extends DataSource
object 照合済未収収益TRN extends DataSource
object 照合済仕入TRN extends DataSource
object 照合済請求TRN extends DataSource

object 仕入データ extends DataSources(仕入明細データ, 仕入返品データ, 費用振替データ, 売価変更データ)
object 修正データ extends DataSources(修正在庫振替TRN, 修正未収収益TRN, 修正在庫移動TRN, 未払計上TRN)
object 売価変更在庫変更TRN extends DataSource
object 仕入データTRN extends DataSources(仕入TRN, 在庫振替TRN, 在庫移動TRN, 未収収益TRN)
object 残高更新TRN extends DataSources(計上済仕入TRN, 計上済未収収益TRN, 計上済未払費用TRN, 更新済買掛残高TRN)
object 請求TRN extends DataSource
object 会計データTRN extends DataSources(請求エラーTRN, 支払不可消込TRN, 支払可消込TRN, 照合済支払費用TRN, 照合済未収収益TRN, 照合済仕入TRN, 照合済請求TRN)


case object 仕入データ取り込み extends Operation(仕入データ)(仕入データTRN, 売価変更在庫変更TRN)
case object 残高更新 extends Operation(仕入データTRN, 修正データ)(残高更新TRN)
case object 照合処理 extends Operation(残高更新TRN, 請求TRN)(会計データTRN)

object 会計処理バッチ extends Flow(仕入データ, 修正データ, 請求TRN)(会計データTRN, 売価変更在庫変更TRN) {
  start proc12(仕入データ取り込み()(out2)) proc21(残高更新(in2)()) proc21(照合処理(in3)()) end
}

不整合時のエラーの例

照合処理の入力(残高更新TRNと請求TRN)を入れ替えて、エラーが出るかどうかを見てみる。

case object 照合処理 extends Operation(請求TRN, 残高更新TRN)(会計データTRN)

以下、1・3は見易いように整形しているので、実際はもっと酷い(苦笑)

ひしだま版1
dsl1.scala:167: error: inferred type arguments [
	object jp.hishidama.asakusafw.dsl1.請求TRN,
	object jp.hishidama.asakusafw.dsl1.NullDataSource,
	Nothing,
	Nothing
] do not conform to method apply's type parameter bounds [
	PI2 <: object jp.hishidama.asakusafw.dsl1.残高更新TRN,
	PI3 <: object jp.hishidama.asakusafw.dsl1.NullDataSource,
	PO2 <: object jp.hishidama.asakusafw.dsl1.NullDataSource,
	PO3 <: object jp.hishidama.asakusafw.dsl1.NullDataSource
]
Error occurred in an application involving default arguments.
  start proc12(仕入データ取り込み()(out2)) proc21(残高更新(in2)()) proc21(照合処理(in3)()) end
                                                             ^
procメソッドでは、in1とout1は
Operationのものがそのまま使われる
という前提で、型引数にしていない。
なのでここで表示されているのはin2・out2以降。
したがってこのエラーは、
照合処理のin2が残高更新TRNであるべきなのに請求TRNである
というエラー。
分かりにくいので、in1・out1も型引数を付けるべきか^^;
ちなみに「^」の位置がかなりずれてるね。
ひしだま版2
dsl2.scala:135: error: type mismatch;
 found   : object jp.hishidama.asakusafw.dsl2.残高更新TRN
 required: object jp.hishidama.asakusafw.dsl2.請求TRN
    in1 = 残高更新.out1
               ^
dsl2.scala:136: error: type mismatch;
 found   : object jp.hishidama.asakusafw.dsl2.請求TRN
 required: object jp.hishidama.asakusafw.dsl2.残高更新TRN
    in2 = flow.in3
               ^
これはエラーメッセージも分かり易い。
ひしだま版3
dsl3.scala:129: error: inferred type arguments [
	object jp.hishidama.asakusafw.dsl3.残高更新TRN,
	object jp.hishidama.asakusafw.dsl3.請求TRN,
	object jp.hishidama.asakusafw.dsl3.NullDataSource
] do not conform to method apply's type parameter bounds [
	A1 <: object jp.hishidama.asakusafw.dsl3.請求TRN,
	A2 <: object jp.hishidama.asakusafw.dsl3.残高更新TRN,
	A3 <: object jp.hishidama.asakusafw.dsl3.NullDataSource
]
Error occurred in an application involving default arguments.
    val 照合処理flow = 照合処理(残高更新flow.out1, flow.in3)
                   ^
これは型引数が入れ替わっている感じがするエラーになっている。
これも「^」は照合処理を指している気がする。

ソースファイルの文字コードはShift_JIS(MS932)、コンパイルはWindowsXPのコマンドプロンプト上でscalacを使って行った。

>scalac -encoding MS932 dsl1.scala

で、コンパイルエラーが起きた箇所が「^」で示されるようになってるけど、日本語(いわゆる全角文字。表示上は半角2文字分の幅をとる)も半角文字と同じく1文字と数えていて、位置がずれている気がする。


DSLのライブラリー

作ってみたライブラリー。

hsdsl.zip (3.4kB)


Scala実験へ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま