全国 QuickCheck 系ユーザの皆様こんにちは、もみあげです。
今日は
CoArbitraryとScalacheck - scalaとか・・・
に関連して、各言語の QuickCheck 実装の CoArbitrary や Arbitrary (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 を変更するものです。
詳しく知りたい場合はソースコードを読みましょう。
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 に包まれた Gen を Monad な値を生成する 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/Gen.java#L637
promote は Monad ではなく、1引数関数クラス F が生成される Gen を返します。
purescript-quickcheck
https://github.com/purescript/purescript-quickcheck/blob/v0.4.0/src/Test/QuickCheck.purs#L25-L26
promote が repeatable という名称なのと、 Monad ではなく関数が対象です。
idris-quickcheck
Arbitrary がほしいなら双対も一緒に定義しろ、という潔さです。
SwiftCheck
promote は Monad でも関数でもなく Rose が対象です。
Persimmon.Dried
今日*1実装しました。
https://github.com/persimmon-projects/Persimmon.Dried/pull/2
たぶん動いています。
Gen.Apply が 'T option を返すので、こいつをなんとか 'T にする苦肉の策として こんな感じにごまかしてます。
FsCheck
考え方はかわらないけど、実装が根本から異なるので気にしない方向で。
まとめ
CoArbitraryの考え方は共通している- Idris のみ
Arbitraryでcoarbitraryの実装を要求する variant関数はシグネチャは同じだが実装はばらばら(乱数生成器に依存する?)promoteはMonadか関数かRoseが対象- 実装はそんなに難しくない?
だいたい似たような実装になっていますが、Gen の構造が他と異なる Persimmon.Dried が若干強引な印象です。
実際、Persimmon.Dried の移植元である scalacheck でも以下の issue で同様の指摘がなされています。
https://github.com/rickynils/scalacheck/issues/136
scalacheck の動向に注視したいところですね。
それはそれとして、Functional Java やばい(褒め言葉)。
*1:ブログ投稿日 or pull request マージ日参照