テストという存在の型について考えてみる

いわゆるユニットテストフレームワークに現れる各種型について考えてみる。

成果は考えない。

このエントリは考えがまとまっていない段階で書かれたものであるため、間違っている可能性があります。

成果物(の途中経過)

途中結果を素っ飛ばした作成中のものは下記に公開している。

https://github.com/pocketberserker/dog/tree/d0ad112e00c72500a2ac5df764b7109249e684de

ドッグフーディングしたいのでこの名前になっただけである*1

この記事では Scala with Scalaz と 一部 FSharp(というか Persimmon )を使って考察する。

テストケースは Kleisli

テストケースは入力値、実行事前条件、期待値、実行事後条件から構成されるはずだ。 このうち入力値、実行事前条件、実行事後条件は型の内部に隠せるものなので、たぶんよくあるテストケースはこんな感じになる。

trait TestCase {
  def run(): TestResult = {
    ???
  }
}

しかし、テスト結果を合成したい場合にはこれは圧倒的に情報が足りないので、テスト結果に値を持てるようにしたほうがよさそうである。

trait TestCase[A] {
  def run(): TestResult[A] = {
    ???
  }
}
// 型コンストラクタはおいおい
trait TestResult[A]

さて、これでTestCase#run は 引数なしで実行できTestResult[A]を返す操作が行える存在になった。

ここでちょっと考えると、引数なしというのは Unit を引数にとる、と考えられなくもない。 そうすると都合の良いことに、Kleisliの構造に一致する。

import scalaz._

type TestCase[A] = Kleisli[TestResult, Unit, A]

// ちょっと不恰好だが、以下のように実行できる
// testcase.run(())

テスト結果はMonadにできなくもない

テスト結果は多くの場合、成功か失敗かで表されるはずだ。

case class Success[A](value: A) extends TestResult[A]
case class Failure[A](reason: String) extends TestResult[A]

しかし、これは色々と大雑把な構造になっている。

  1. エラーが考慮されていない
  2. 失敗時の情報が蓄積できない
  3. "失敗"なのか"テストをスキップ(無視)した"のか判断がつかない

問題1を解決するためにエラーを持てるようにする。

case class Done[A](result: String \/ A) extends TestResult[A]
case class Error[A](e: Throwable) extends TestResult[A]

テスト実行完了か、エラーが発生したことが確認できるようになった。 この時点では明らかにMonadである。

問題2について解決を試みる。

case class Done[A](results: ValidationNel[String, A]) extends TestResult[A]
case class Error[A](e: Throwable) extends TestResult[A]

成功時に保持すべき値は最終結果ひとつで良いのに対し、失敗情報は多数保持すべきなのでValidationNelがちょうどよさそうだ。 ただし、実装次第ではMonad則を満たさなくなりそうである。

問題3が残っている。 これはValidationNelだけでは解決できそうにないので、別の型を導入する。

trait Cause
case class Violated(reason: String) extends Cause
case class Skipped(reason: String) extends Cause
trait AssertionResult[A]
case class Passed[A](value: A) extends AssertionResult[A]
case class NotPassed[A](cause: Cause) extends AssertionResult[A]
type AssertionNel[A] = NonEmptyList[AssertionResult[A]]
case class Done[A](results: AssertionNel[A]) extends TestResult[A]
case class Error[A](e: Throwable) extends TestResult[A]

Done#results に入る値を特定のものに絞り(ありえない状態を除外する、といったほうが正しい)、それ以外の入力は受け付けないようにすればTestResult[A]Monad則を満たす。 しかしその実装はMonadと呼べるのかというのは、疑問が尽きない。

Persimmon の TestResult は Monad ではない

今回はエラーだった場合はそれ以降何もしないことにしたので、Monadになる可能性があった。 しかし、実行できるアサーションはすべて実行してしまえ、という方針にするとMonad則を満たせなくなる。

Persimmon はそういう方針なので、おそらくMonad則を満たさない。

AssertionNel ~> TestResultは実装できるか

これに関しては考え方次第である。

  • テストケース名や入力値をテスト結果に持たせたいなら、無理
  • 変換時は空のデータを持たせ、Runnerで後から差し替えるならできそう

検証が足りていないのでもう少し考慮すべき項目があるかもしれない。

Persimmonに関していえば、変換できなくもない。 テスト名が空な場合にはリフレクションで変数(関数)名前を取得して入れ替える機能が備わっているためだ。

Assertion[A]は必要か

以下のような型を定義できる。

type Assertion[A] = Kleisli[AssertionResult, Unit, A]

しかし必要性を感じない。 基本的にさっさと実行されてしまうため、機能過多に思える。 これが何かに使えるか考えるなら、AssertionResult[A]の機能拡充を考えたほうがよさそうである。

TestResult[A] は Applicative であるべきか

現時点のTestResult[A] は入力値を制御しているためMonad則を満たすが、Applicativeにとどめておくべきかどうかは悩ましい問題だ。

Scalaのfor式は F# のコンピュテーション式に比べて表現に不安を感じるし、テスト失敗情報を蓄積させるならMonadにすべきではないのはその通りだ。 しかし、ScalaだとApplicativeで合成するのはそれはそれで面倒くさい作業にしかならない気もしている。

書きやすいDSLを実装する、という点ではScalaではマクロを使うことも考慮すべきなのだろうが…さてはて。

まとまらない

  • FreeやFreeApを使って何かできないか考えていたらいつのまにかこういうものに行き着いた、なぜ
  • Operational monad などの利用を考えると、やはりHaskellのライブラリを移植したほうが早いのだろうか…
  • とはいえ、Scalazの各種型で色々定義できそうということがわかっただけでも面白かったので良しとする

*1:猫の双対でco猫とかにしてみたかったけどどう見ても名前負け思想だったのでやめた

"なぜshapelessにはGeneric[Boolean]がないの?"と尋ねてみた

Gitterで質問したのでそのメモ*1。 なお私の英語は壊滅的なので片言?なのは許してください。

Hello. Why does Generic[Boolean] instance not exist?

と投稿したところ、milessabinさん(shapeless作者)から

Because there's no obvious sum of product representation of Boolean.
I guess you would want false.type :+: true.type :+: CNil?

と返答があった。なるほど。

@milessabin Thank you. I understand.
I try to port from Haskell code(using GHC.Generic) to Scala code. GHC.Generics derive some instances of Generic a.

とりあえずこういうことしようとしたんだよーと言ったところ

@pocketberserker gotcha.
The idiom here is to hand-write instances for primitives and use Generic for ADTs.
I would be very interested to see a side-by-side comparison of something written with GHC generics and something written with shapeless.
Would you be able to blog about it?

ほえーと思ってたらなんか後日記事かくことになった。

まぁ、最初から無理っていうのもなんだし、やるだけやってみよう…。

*1:という名のバックアップ

HaskellのlogictをScalaに移植してみた

Haskell に logict というライブラリがあります。

logict: A backtracking logic-programming monad. | Hackage

MonadLogicやLogicTが何者なのかはそのうち解説記事を書くとして、今回はこのライブラリをScalaに移植してみました。

pocketberserker/scala-logic · GitHub

そのうちファーストリリースを出す予定です。

このライブラリを作ったのは、smallcheckをscalaで実装してみたいと思ってコードをみたらLogicTが必要だとわかったためです。

Haskellのlogictとの違い

若干ですが差異があります。

  • 型パラメータの順序
  • 引数の順序
  • logic関数は未実装(RankNTypesに阻まれた)
  • いくつか制約を緩めている

制約を緩めているというのは、例えばMonadPlusではなくApplicativePlusでよい、とかですね。

これに関しては私もpull requestをもらうまで気がついていなかったのですが、確かに緩められるものは緩めてもよいですね。

functional dependenciesについて

LogicTのMonadReaderインスタンス実装は@xuwei_kさん、@halcat0x15aさん、@gakuzzzzさんに手伝ってもらいました、ありがとうございます*1

functional dependenciesをScalaで実現できるのか自体知らなかったので、とても助かりました。 みなさんScala函数型なことでわからないことがあったら以下のgitter部屋に突撃するといいと思います。

scalajp/functional - Gitter

なぜScalaz?

某猫kitsの利用も考えたのですが、まだリリースされていなかったり今回の実装に必要そうな機能がなかったりしたので見送りました。 あとはまぁ、Scalazを長らく触っていなかったのでリハビリしたかったとか(某本もでることですし)。

Scalazは長い間開発が続いているだけあって機能が充実していて良いですね。 そういう意味では当分使われ続けるのではないでしょうか。

まとめ

小さいライブラリですがscalazの機能を色々と使っているので、ちょっとした勉強にはなるかもしれません。

*1:よく考えたら全員去年のScalaz勉強会の発表者だ

AppVeyorのSQL ServerにJDBC Driverで接続する

備忘録です。 簡単な経緯は以下の通り。

rpscala でそういう話題があったのに関連して、 AppVeyor で頑張れないかを xuwei さんのコードを引き継いで試してみました。

参考資料

だいたいこの辺りを情報を AppVeyor で実行できるようにしていきます。

設定やスクリプト

説明が面倒なので結論だけはります。 設定値やプロジェクト名もそのままになっているので、適宜読み替えてください。 また、今回の内容と直接関係のない部分は削っています。

あと、sbtはリポジトリに同梱しているパターンで試しているので、同梱したくない場合はsbtをダウンロードする必要もあります。

# appveyor.yml
services: mssql2014
build_script:
- netsh advfirewall firewall add rule name="jdbc driver for sql server udp" dir=in action=allow protocol=UDP localport=1434
- sc config sqlbrowser start= auto
- net start sqlbrowser
- ps: Set-ExecutionPolicy RemoteSigned
- ps: .\Setting-SqlServer.ps1
- net stop "SQL Server (SQL2014)"
- net start "SQL Server (SQL2014)"
- netsh advfirewall firewall add rule name="jdbc driver for sql server tcp" dir=in action=allow protocol=TCP localport=1433
- sqlcmd -S ".\SQL2014" -U sa -P Password12! -Q "create database scalikejdbc;"
- del scalikejdbc-core\src\test\resources\jdbc.properties
- move scalikejdbc-core\src\test\resources\jdbc_sqlserver.properties scalikejdbc-core\src\test\resources\jdbc.properties
- ps: wget http://download.microsoft.com/download/0/2/A/02AAE597-3865-456C-AE7F-613F99F850A8/sqljdbc_4.1.5605.100_enu.tar.gz -OutFile "sqljdbc.tar.gz"
- 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar.gz
- 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar
- ps: mkdir .\scalikejdbc-core\lib
- ps: cp .\sqljdbc_4.1\enu\sqljdbc41.jar .\scalikejdbc-core\lib\
// Setting-SqlServer.ps1

// バージョンに合わせて ComputerManagementの値を差し替える
// SQL Server 2008 -> ComputerManagement10
// SQL Server 2012 -> ComputerManagement11
Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocol |
  Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp'} |
  ForEach-Object {$_.SetEnable()}

$tcpProperties =
  Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocolProperty |
    Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp' -and $_.IPAddressName -eq 'IPAll'}

foreach( $tcpProperty in $tcpProperties ){
  $requestedValue = ""
  if($tcpProperty.PropertyName -eq "TcpPort"){
    $requestedValue = "1433"
  }
  $tcpProperty.SetStringValue($requestedValue)
}

SQL Server 2014 Espressで試していますが、たぶん SQL Server 2008あたりまでは普通に動きそうな気がします。

重要なところ?

SQLServer用のjarを同梱するのはライセンス的につらい感じがしたので、PowerShell 3.0から使える wget エイリアスコマンドを使ってダウンロードし、AppVeyor にインストールされている 7Zip で解凍するのがミソです。

あと、各種設定をして再起動する前にデータベースを作成してもログイン権限がなくてテーブルにアクセスできないかも?

まとめ

今回はsbtで作業しましたが、gradleでもできるのではないでしょうか。

懸念事項は、AppVeyorの実行速度が遅いことと、mysqlやpotgresql前提で書かれたSQLSQL Serverで通らないことがまれによくあることです。

scalacheckの内部構造をいじって遊んでみた

SPECIAL DAY Scala Hack-a-son 143th in KABUKIZA - Scala Meetup Group in Tokyo | Doorkeeper

たまにはハッカソンもいいかなと思って参加してみました(噂の rpscala に参加してみたかったというのもある)。

で、当日は scalacheck をいじっていたコードがようやくテストに通るようになったので書き残しておきます。

注意事項

この記事のコードは pull request を送るつもりはないので、そのことを認識して読んでください。

今のところ scalacheck を fork して別プロジェクトとして育てるつもりもないです。

やりたかったこと

基本方針は "いらないものを消す" です。

  1. scalajs の クロスコンパイル設定を有効にする
  2. Gen で Option[T] ではなく T を返すようにする
  3. 内部表現 R trait を削除する
  4. scalaz で置き換えられそうなコードを置き換える
  5. ランダムを rng か spire/random に差し替える

現時点でできたのは R を消すまでです。

ロスコンパイル設定

https://github.com/pocketberserker/scalacheck/commit/c7c47204c7245d1a9830aca79b8550b9e63052fc

2015/02/12 時点の scalacheck の master ブランチは Windows 上ではそのままだとビルドできません。 クロスコンパイルのためのコード共有を、Windows で認識できないシンボリックリンクで行っているためです*1

対処法は sbt-scalajs の要求にあわせてディレクトリ構成変えたり設定変更した程度です。

これに関してはハッカソンの数日前に作業してました。

Gen で T を返す & R trait を削除する

https://github.com/pocketberserker/scalacheck/commit/6d03207ceee9caf6a9e7ad2cb518c532372bd826

quickcheck 系でデータの生成に失敗することはそうそうないので、 Option を返さないようにしました。

…が、ここで suchThat メソッドの実装でスタックオーバーフローしたり無限ループに陥ったりして、全然進まず。 結局当日の進捗は駄目でした。

後日、よく考えたら一部実装の変更に伴い suchThat を呼び出すべきではない部分を発見したので、これを削除したら動くようになりました。

R trait は Gen 用のラベルや sieve を持っているのですが、これもいらないだろうということで削除。

リファクタリングみたいなこと

https://github.com/pocketberserker/scalacheck/commit/e923d97a2ddfeb9b158bcb9524ce4ca844b7eb32

scala.util.Random から別のものに差し替えたいな、と思って依存関係だけ設定したのですが、作業できずじまい…。

Arbitrary 削除断念

CoArbitrary を自前実装してしまったので、消すのも変だなと思い残してます。

その他

値が生成できるまでがんばってしまうので、多少実行速度が劣化したような感覚… filtersuchThat のご利用は計画的に。

まとめ

rpscala は懇親会も含め楽しかったです。

*1:Windows にもシンボリックリンク自体は存在した気がする

CoArbitraryの実装比較

全国 QuickCheck 系ユーザの皆様こんにちは、もみあげです。

今日は

CoArbitraryとScalacheck - scalaとか・・・

に関連して、各言語の QuickCheck 実装の CoArbitraryArbitrary (a -> b) インスタンスについてみていきましょう。

CoArbitrary とは

QucikCheck のドキュメントによると、関数値の生成に使われる型クラスらしいです。

-- Haskell での定義
class CoArbitrary a where
  coarbitrary :: a -> Gen b -> Gen b

値と Gen から新たな Gen を生成できます。 第2引数の Gen が必要なのは、最終的な値を生成するための Gen を作るために、乱数生成器が必要だからだと思ってます。

CoArbitraryインスタンスを作るときには、 variant という関数を用いることが多いです。

-- Haskell での定義
Integral n => n -> Gen a -> Gen a

この関数は Gen の seed を変更するものです。

詳しく知りたい場合はソースコードを読みましょう。

Test/QuickCheck/Arbitrary.hs

Arbitrary (a -> b)

では、僕らの欲したインスタンスを見てみましょう。

-- Haskell での定義
instance (CoArbitrary a, Arbitrary b) => Arbitrary (a -> b) where
  arbitrary = promote (`coarbitrary` arbitrary)

promote 関数という関数は

http://hackage.haskell.org/package/QuickCheck-2.7.6/docs/Test-QuickCheck-Gen-Unsafe.html

Monad に包まれた GenMonad な値を生成する Gen にあげる関数です。 lift と思ってもよさそうに見えますね。

promote に適用する関数値の型は a -> Gen b であり、 関数も Monad なので条件を満たしています。

流れをまとめると

  • 適用された値をから戻り値を生成するジェネレータの seed を変更する関数を作る
  • promote で lifting させる
  • あたかも関数を生成したようにみせかけて property に渡す
  • 関数適用したら Gen から戻り値がランダムに生成される

といったところでしょうか。

なお、一連の情報からこのインスタンスから得られる関数値の適用結果を使って何かテストするのは避けるべきということが伝わってくる気がします。

実装比較

てきとーに解説を済ませたところで、実装をみていきましょう。

Functional Java

https://github.com/functionaljava/functionaljava/blob/v4.2/core/src/main/java/fj/test/Coarbitrary.java

https://github.com/functionaljava/functionaljava/blob/v4.2/core/src/main/java/fj/test/Gen.java#L637

promoteMonad ではなく、1引数関数クラス F が生成される Gen を返します。

purescript-quickcheck

https://github.com/purescript/purescript-quickcheck/blob/v0.4.0/src/Test/QuickCheck.purs#L25-L26

https://github.com/purescript/purescript-quickcheck/blob/d464c5c31313ea6737ac46a8335596eeefe74550/src/Test/QuickCheck/Gen.purs#L49

promoterepeatable という名称なのと、 Monad ではなく関数が対象です。

idris-quickcheck

https://github.com/david-christiansen/idris-quickcheck/blob/51b7f619a3203c20ef8d8e30007bd83dba9fd386/QuickCheck.idr#L96

Arbitrary がほしいなら双対も一緒に定義しろ、という潔さです。

SwiftCheck

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Arbitrary.swift#L365-L367

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Gen.swift#L115

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Combinators.swift#L13

promoteMonad でも関数でもなく Rose が対象です。

Persimmon.Dried

今日*1実装しました。

https://github.com/persimmon-projects/Persimmon.Dried/pull/2

たぶん動いています。

Gen.Apply'T option を返すので、こいつをなんとか 'T にする苦肉の策として こんな感じにごまかしてます。

FsCheck

https://github.com/fsharp/FsCheck/blob/3d7f4d47d6cff65ecdbcc31e60f007b54012de9f/src/FsCheck/Arbitrary.fs#L482

考え方はかわらないけど、実装が根本から異なるので気にしない方向で。

まとめ

  • CoArbitrary の考え方は共通している
  • Idris のみ Arbitrarycoarbitrary の実装を要求する
  • variant 関数はシグネチャは同じだが実装はばらばら(乱数生成器に依存する?)
  • promoteMonad か関数か Rose が対象
  • 実装はそんなに難しくない?

だいたい似たような実装になっていますが、Gen の構造が他と異なる Persimmon.Dried が若干強引な印象です。 実際、Persimmon.Dried の移植元である scalacheck でも以下の issue で同様の指摘がなされています。

https://github.com/rickynils/scalacheck/issues/136

scalacheck の動向に注視したいところですね。

それはそれとして、Functional Java やばい(褒め言葉)。

*1:ブログ投稿日 or pull request マージ日参照

sonatypeのアカウントを作成してscodec-msgpackをpublishした

数日前(?)に xuwei さんが ,msgpack4z というライブラリをリリースしたことが(少なくとも私の中では)話題です。

msgpack4zというmsgpackのScala用ライブラリを作った - scalaとか・・・

こちらの開発が続くだろうし、私が作った scodec-msgpack はリリースせずお蔵入りしようかとも考えましたが

これは確かにその通りだと思ったのと、

という状況もあるので、一旦このタイミングでリリースしておくことにしました。

リリースのための準備(アカウント作成等)

で、リリースのために sonatype アカウントを作成してビルド設定をいじりました。

この2つの記事が非常に参考になりましたというか、ほぼそのままです。 ちなみに Windows(Surface Pro 3) で msysgit な環境で作業しました。

確かに、アカウント登録は面倒ですけど、それ以降は楽でしたね。 sbt-sonatype, sbt-release パワーでしょうか。 そういえば主要リポジトリを見ていると最近は sbt-buildinfo プラグインも使うプロジェクトが多いみたいですね。

scodec-msgpack の今後?

xuwei さんの記事の

  • データ型それぞれ作るの面倒
  • 中間データ発生して効率よくなさそう(計測したわけではない)

この記事はわりと同じ感覚です。 これだけの型を作るのは面倒ですし、パターンマッチとかで処理が増えてしまうので…。

とはいえ、純粋関数型的アプローチでどこまでパフォーマンスを得られるかは気になるので、対応できる部分があれば修正していきたいとは思っています。

(scodecが早くなればタダ乗りできますし)


あと記事と全然関係ないけど各種msgpackライブラリで任意回データを投げ合ってvalidかどうか(いつの仕様ならvalidか)一覧にするアプリ作ってみたくなってきた。