CoArbitraryの実装比較
全国 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 マージ日参照