a wandering wolf

Does a wandering wolf dreams of a wondering, sometimes programming sheep?

このエントリーをはてなブックマークに追加

FsCheck と ScalaCheck とを見比べる

これは F# Advent Calendar 2014 31日目の記事です…えっ、アドベントカレンダーってクリs(ry

ちなみに前日は @zecl さんの『F# Build Tools for Unity(ゲームのやつ) - UniFSharpのご紹介』でした。

はじめに

本記事では、ランダムテスティングツールである QuickCheck の F# 実装である FsCheck と Scala 実装である ScalaCheck の共通点や違いを、実装面からいろいろ見ていこうと思います。

なぜ私がこんなものを書いているのかよく分からないのですが、

という流れがあって、今ここにいます。ぱぴろん?よく分からないですね…。

プロパティの書き方

ScalaCheck のプロパティはシンプルに定義できます。ユーザーガイドの例を見てみましょう:

import org.scalacheck.Prop.forAll

val propConcatLists = forAll { (l1: List[Int], l2: List[Int]) =>
  l1.size + l2.size == (l1 ::: l2).size }

propConcatLists.check

明示的にプロパティを作っており、実行は check メソッドを呼び出しています。

一方の FsCheck はこんな感じ:

open FsCheck

let revRevIsOrig (xs:list<int>) = List.rev(List.rev xs) = xs

Check.Quick revRevIsOrig

定義しているのは bool を返す普通の関数ですが、これを Check.Quick に渡すことで実行しています。FsCheck のソース上もあっち行ってこっち行ってという感じで、初めて読む人にはなかなか厳しいものがあります。(後述参照)

テストの実行

前節でも少し述べましたが、ScalaCheck はプロパティの check メソッドを実行することで、FsCheck は Check クラスの Quick メソッドなどを呼び出すことで、プロパティに対してテストを実行します。どうでもいいですが、FsCheck の Check はモジュールじゃなくてクラス定義なんですね。

ScalaCheck の check メソッドは、実際にはシングルトンオブジェクト Testcheck メソッドを呼び出しています。内部の呼び出しの中でデフォルトのテストパラメータ(Test.Parameters.default)を渡し、テスト実行の条件などを設定しています。

FsCheck でテストを実行するときの静的メソッド群、例えば Check.Quick でも、内部呼び出しでこっそりとデフォルトのテストパラメータ(Config.Quick)を渡しています。この辺りは似たような感じですね。

Arbitrary

Arbitrary はテストを実行する際にプロパティに渡す引数を生成するものです。QuickCheck 系ツールがランダムテスティングを実現するための、キモになる部分と言っていいでしょう。

ScalaCheck ではプロパティを作る際、例えば forAll だと内部で forAllShrink呼び、暗黙的に Arbitrary インスタンスを生成しています。(ここ(Arbitrary.scala#L70)から下のコードを読んでみましょう)

FsCheck の場合は、「そんなに単純ではなく」、謎の技術を使い、ここ(Property.fs#L208)Arbitrary のインスタンスを生成しています。詳しくはここ(TypeClass.fs#L182)からどんどん TypeClass.fs を読んでみてください。私は吐き気がしました。

なお、 Arbitrary インスタンスの構成もそれぞれ少し違っています。ScalaCheck では Gen インスタンスをメンバーに持つだけの型ですが、FsCheck では Gen インスタンスの他に「シュリンカ」と呼ばれる変換もメンバーに持っています。ScalaCheck では、これは別のモジュールにあります。てか、何で FsCheck の Arbitrary は Gen.fs にあるんだよ…。

おしまい

読めば読むほど「言語自体の文法は特殊だけど、実装は意外にシンプルな ScalaCheck」と、「言語自体の文法は比較的シンプルだけど、実装は特殊というかエグい FsCheck」という感想が導かれます。どちらも「自分たちの言語でできること」を頑張った結果だと思います。FsCheck なんて、関数型で頑張りたい F# er のつらみがよく表れたコードでしたね…。

これだけおかしなことをやっている FsCheck ですが、一部リフレクションを使いつつも、可能な限り型の恩恵に与り、型の力を引き出そうとしています。皆さんの F# ライフをさらに豊かにする可能性のある(黒魔術に染まる可能性もある)コードですので、この年末年始に読み込んでみるのはいかがでしょうか?

それでは、2015年も皆さんにとって良い年になりますように。