パート2.ジェネレーター
このシリーズの入門記事で、ジェネレーターを既に知っていることを願っています。 このチュートリアルでは、学習した知識を統合し、独自の(再帰を含む)ジェネレーターの作成方法を学習します。 ジェネレーター専用ですが、プロパティについても忘れません。 さらに、それらを積極的に使用して、ジェネレーターメカニズムのフルパワーを実証します。 前提条件のメカニズムを検討してください。 おそらく、シリーズの2番目の記事をプロパティに当てる方が論理的であり、おそらくこれが正しい決定でしょう。 しかし、私の個人的な観察によると、最も困難なのはジェネレーターです。 次の記事でプロパティを検討します。
サイクル構造
発電機
Prop
オブジェクトのforAll
メソッドは、ランダムに生成されたデータを使用します。 単純なデータ型またはコンテナ( 複合 )型のいずれかです。 ScalaCheckでは、ジェネレーターはGenの子孫です。
sealed abstract class Gen[+T] { def apply(prms: Gen.Params): Option[T] ... }
クラスは、生成される値のタイプによってパラメーターが決まります。 したがって、文字列にはGen[String]
があり、悪名高いPerson
- Gen[Person]
ます。 必要な影響が範囲内であれば、 Arbitrary
オブジェクトのarbitrary
メソッドを使用することもできます。
ジェネレーターに暗黙的なサポートを追加します
暗黙のことを話しているので、 arbitrary
あなたのタイプに。 まず、型自体が必要です。
sealed trait LED case object Red extends LED case object Green extends LED case object Blue extends LED
次に、ダイオード用のジェネレーターを作成します。
val ledGenerator: Gen[LED] = Gen.oneOf(Red, Green, Blue)
ここで、ジェネレーターからimplicit
Arbitrary
インスタンスを作成します。
implicit val arbitraryLed: Arbitrary[LED] = Arbitrary(ledGenerator) Arbitrary.arbitrary[LED].sample // Some(Green)
これで、スコープ内にLEDがあるので、いくつかのプロパティを確認できます。
val ledProp = forAll { diode: LED => // }
多数のプリミティブ型と標準ライブラリのコンテナの場合、暗黙的な変換はArbitrary
オブジェクト内ですでに事前定義されています。
import org.scalacheck.Arbitrary.arbitrary val arbitraryString = arbitrary[String] val arbitraryInteger = arbitrary[Int]
Gen.resultOf
は、この問題であなたを大いに助けることができます。 この関数は、 arbitrary
値が既に定義されているコンストラクターパラメーターに対して、ケースクラスを受け入れます。
case class Coord(x: Double, y: Double) // resultOf. val genCoord = Gen.resultOf(Coord) // Arbitrary. implicit val arbitraryCoord = Arbitrary(genCoord) // . val prop = Prop.forAll { coord: Coord => //... }
sample
方法
残念ながら、 toString
メソッドはGen
クラスのインスタンスに対して定義されていませんが、より便利で便利なメソッドがあります。 ジェネレーターを作成するときには、非常に頻繁に使用する必要さえあります。 これはGen.sample
メソッドです。 Option
内で生成された値の例を返します。
// ScalaCheck , // 10 . Arbitrary.arbitrary[String].sample map (_ take 10) // . // Some(Q冋执龭轙⮻拍箘㖕) Arbitrary.arbitrary[Double].sample // Some(-5.180668081211655E245)
変換
前のパートですでに説明したように、 map
メソッドはジェネレーターにも適用できます。 これを次の例で説明します。
val octDigitStr = choose(0, 7) map (_.toString) octDigitStr.sample // Some(3) octDigitStr.sample // Some(5)
フィルタリング
ジェネレーター用のfilter
メソッドもありますが、実際には、これはsuchThat
メソッドを呼び出すための単なるエイリアスです。
val evenOct = octDigit.filter(_ % 2 == 0) evenOct.sample // None // , : evenOct.sample // None // , ? evenOct.sample // Some(2)
上記の例は、 filter
やsuchThat
使用しない理由を示しています。 新しいジェネレーターは、フィルターを通過しない値を単に拒否します。 これにより、テストプロセスが大幅に遅くなります。
前提条件はフィルターと同様に機能します。
前提条件
ScalaCheckの含意操作によってロジックで提示される前提条件は、プロパティ内で使用されるメソッドです。 入力を制限することができます。 演算子==>
によって書かれました。 前提条件を使用するには、 Prop.propBoolean
に追加する必要がありProp.propBoolean
。 したがって、偶数の場合、最後の桁も偶数になるというステートメントを確認しましょう。
import org.scalacheck.Prop.{forAll, propBoolean} def isEven(number: Int) = number % 2 == 0 // . def lastChar(number: Int) = number.toString.last.toInt // . val p2 = forAll(arbitrary[Int] suchThat isEven) { number => isEven(lastChar(number)) } // . val p1 = forAll(arbitrary[Int]) { number => isEven(number) ==> isEven(lastChar(number)) }
プロパティを開始するには、 check
機能を使用できます。
// p1.check // + OK, passed 100 tests. // p2.check // + OK, passed 100 tests.
フィルターが硬いほど、より多くの値が無視されます。 これは、値の小さなセットまたは互いに非常に離れている値の場合に重要になることがあります。 たとえば、範囲Intの素数。 このような取り組みから、ScalaCheckは簡単に疲れて、あなたに降伏することができます。
scala> p1.check ! Gave up after only 1 passed tests. 100 tests were discarded
偶数の例ではこれに気付かないでしょうが、単純な数の例では簡単です。 次のことも知っておく必要があります。
偶数のフィルターはさまざまな方法で実装できます。 たとえば、あなたは取ることができます
任意の整数で、2を掛けます。素数の場合
事前にカウントする方がはるかに簡単です。たとえば、
エラトステネスのふるい 、そしてリストを突き刺す
Gen.oneOf
内の計算値。これについては後で説明します
記事。
suchThat
を使用してジェネレーターをsuchThat
、問題も発生する可能性があります。 そのような場合は、 retryUtil
メソッドを使用する必要があります。 suchThat
とは異なり、ScalaCheckの動作が難しくなり、無限ループに陥ることがあります。 retrtyUtil
がretrtyUtil
fiter
またはsuchThat
似ていsuchThat
:
arbitrary[Int] retryUntil isEven
また、 fromOption
flatten
とflatten
、およびfromOption
がDeprecated
と宣言されていることにfromOption
かもしれません。 これは、空のジェネレーターを作成する誘惑を少なくするために行われます。
独自のジェネレーターを作成する
前述のように、既存のジェネレーターを理解のために組み合わせることで、独自のジェネレーターを作成できます。
val coordGen = for { i <- arbitrary[Int]; j <- arbitrary[Int] } yield (i, j) coordGen.sample // Option[(Int, Int)] = Some((45,60)) coordGen.sample // Option[(Int, Int)] = Some((29, 37))
任意のデータ構造のジェネレーターを宣言できます。 ADTを使用する単純なケース:
sealed trait LightColor case object Red extends LightColor case object Orange extends LightColor case object Green extends LightColor case object FlashingGreen extends LightColor case object Extinct extends LightColor val colorGen = Gen.oneOf(Red, Orange, Green, Extinct)
人生を複雑にしているなら、徹底的に:
// , . type Color = LightColor object PedestrianTrafficLight { sealed class State(_1: Color, _2: Color) case object Stop extends State(Red, Extinct) case object Move extends State(Extinct, Green) case object FinishMove extends State(Extinct, FlashingGreen) def states: Seq[State] = Seq(Move, FinishMove, Stop) } object VehicularTrafficLight { sealed class State(_1: Color, _2: Color, _3: Color) case object Stop extends State(Red, Extinct, Extinct) case object Ready extends State(Red, Orange, Extinct) case object Move extends State(Extinct, Extinct, Green) case object Warn extends State(Red, Orange, Extinct) case object FinishMove extends State(Extinct, Extinct, FlashingGreen) def states: Seq[State] = Seq(Stop, Ready, Move, Warn, FinishMove) } sealed trait TrafficLight case class PedestrianTrafficLight(state: PedestrianTrafficLight.State) extends TrafficLight case class VehicularTrafficLight(state: VehicularTrafficLight.State) extends TrafficLight
ジェネレーターを宣言します。
import Gen._ // val pedestrianTrafficLightStateGen = Gen.oneOf(PedestrianTrafficLight.states) // val vehicularTrafficLightStateGen = Gen.oneOf(VehicularTrafficLight.states)
そして、歩行者と交通信号からタプルを生成します。 一つだけあります:信号機の状態は相互に排他的であってはなりません。 まず、歩行者が操作できる条件を決定します。 合成によってジェネレーターを作成するため、 retryUntil
代わりにsuchThat
を使用します。
val pedestrianCanGo = pedestrianTrafficLightStateGen.retryUntil { state => state != PedestrianTrafficLight.Stop }
次に、信号機の状態を生成し、それから始めて、歩行者の許容状態を決定し、それらをリストに結合します。 受け取ったジェネレーターのタイプは、 Gen[(VehicularTrafficLight.State, PedestrianTrafficLight.State)]
です。
val states = vehicularTrafficLightStateGen flatMap { case vehState @ VehicularTrafficLight.Stop => pedestrianCanGo map (pedState => (vehState, pedState)) case pedestrianCanNotGo => (pedestrianCanNotGo, PedestrianTrafficLight.Stop) }
オブジェクト自体に信号機のステータスを表示します:
val trafficLightPair = states map { case (v, p) => (VehicularTrafficLight(v), PedestrianTrafficLight(p)) }
結果を確認しますか? 私は受け取った:
(VehicularTrafficLight(FinishMove),PedestrianTrafficLight(Stop))
そして
(VehicularTrafficLight(Move),PedestrianTrafficLight(Stop))
それは全く本当です。 多分どこか私が間違っていた。 主なことは、メカニズム自体を実証することでした。
ScalaCheckでは、再帰的なデータ構造を生成することもできます。
ただし、このプロセスを簡単かつ些細なことと呼ぶことは困難です。 さらにこれで
この記事では、再帰ジェネレーターと、その問題について見ていきます。
それらを使用するときに遭遇するかもしれません。
プロパティのジェネレーター
前述のほとんどの例では、ScalaCheckはジェネレーターの適切なインスタンスを個別に選択し、それを使用してプロパティを計算します。 ただし、ジェネレーターを独自に作成したのはこのためではなく、ScalaCheckはスコープから何かを削除します。 「互換性のある」信号機を覚えていますか? それらをどこかにプッシュしましょう:
import Prop.{forAll, propBoolean} def isRed(trafficLight: VehicularTrafficLight) = trafficLight.state == VehicularTrafficLight.Stop def notRed(trafficLight: PedestrianTrafficLight) = trafficLight.state != PedestrianTrafficLight.Stop // Prop.forAll val canNotBothBeRed = forAll(trafficLightPair) { case (vehicular, pedestrian) => isRed(vehicular) ==> notRed(pedestrian) }
チェックしてみて?
canNotBothBeRed.check
そして、すべてが正常であることを確認します。
+ OK, passed 100 tests.
多くの場合、1つのジェネレーターでは十分ではないため、複数のジェネレーターを使用できます。
val p = forAll(Gen.posNum[Int], Gen.negNum[Int]) { (pos, neg) => neg < pos }
ジェネレーターなどのプロパティは、一緒にネストできるため、上記の例を次のように書き換えることができます。
val p = forAll(Gen.posNum[Int]) { pos => forAll(Gen.negNum[Int]) { neg => neg < pos } }
ネストされたforAll
呼び出しの代わりに、使用するジェネレーターからタプルをコンパイルすることもできます。 また、 forAll
2回呼び出さないでください。
発電機のラベル
タグを使用することをお勧めします。 これにより、エラー報告が改善され、ジェネレーターを使用するコードの可読性が向上します。 ラベルを追加するには、 |:
および:|
使用できます:|
。 ラベルは、文字列または文字のいずれかです。 複数のタグをジェネレータに追加できます。 説明するために、前の例を使用して、 記号を変更し、プロパティを不条理にします。
// |: :| . val p = forAll('positive |: Gen.posNum[Int]) { pos => forAll(Gen.negNum[Int] :| "negative numbers") { neg => neg > pos } }
同意して、良くなった?
! Falsified after 0 passed tests. > positive: 0 > positive_ORIGINAL: 1 > negative numbers: 0
プリミティブジェネレーター
定数
Gen.const
よりも簡単なものをGen.const
はGen.const
です。 任意の値を取り、ジェネレーターに注意深くパックします。 適切なタイミングで適切な場所にあるオブジェクトは、暗黙的にGen
キャストされますGen
したがって、ほとんどの場合、このメソッドを明示的に呼び出す必要はありません。
死ぬ
ScalaCheckは、実行不可能なジェネレーターを返すGen.fail
メソッドを使用します。 sample
メソッドを呼び出そうとすると、常にNone
ます。 ほとんどの場合、彼はあなたの方法で会うことはありません、彼が会った場合、あなたは彼に精通していることを考慮してください:
Gen.fail.sample // Option[Nothing] = None
数字
Gen.posNum
とGen.negNum
を使用して、それぞれ正の数と負の数を生成できます。
import org.scalacheck.Gen import org.scalacheck.Prop.forAll val negative = Gen.negNum[Int] val positive = Gen.posNum[Int] val propCube = forAll (negative) { x => x * x * x < 0 }
Gen.chooseNum
には興味深い機能があります:このジェネレーターは、指定された範囲(包括的)で、ゼロ(指定された範囲内に収まる場合)、最小値と最大値、および提示された値のリストの重みで数値を生成します:
// specials vararg. Gen.chooseNum(minT = 2, maxT = 10, specials = 9, 5)
Gen.choose
を使用して、特定の範囲内の数値を生成できます。 さらに、これは決して失敗しないまれなタイプのジェネレーターです。 シンボルでも使用できます。 キャラクタージェネレーターといえば...
キャラクター
ScalaCheckには多くの文字ジェネレーターがあります。 次から選択するように招待されます。
-
Gen.alphaUpperChar
-
Gen.alphaLowerChar
-
Gen.alphaChar
-
Gen.numChar
-
Gen.alphaNumChar
それでは、「海戦」でゲームの座標を生成してみましょう。
val coord = for { letter: Char <- Gen.alphaUpperChar number: Char <- Gen.numChar } yield s"$letter$number" coord.sample // Some(L1)
理解のおかげで、リスト、タプル、文字列などの任意の集計を生成できます。 行といえば...
行
簡単に生成することもできます。 Gen.alphaStr
は、アルファベット文字からのみ文字列を生成します。 Gen.numStr
は数値文字列を生成します。 Gen.alphaLowerStr
とGen.alphaUpperStr
が異なる文字から文字列を個別に生成することができますGen.alphaLowerStr
とGen.alphaUpperStr
は、これらの目的のために設計されています。 Gen.idenifier
は、常に小文字のアルファベット文字で始まり、その後に英数字が続く空でない文字列を提供します。 一部の文法では、これは非常に識別子です。
import org.scalacheck.Gen val stringsGen = for { key <- Gen.identifier value <- Gen.numStr } yield Bucket(key take 8, value take 2) stringsGen.sample // Some(Bucket(kinioQqg,60))
機能
Scalacheckは関数( Function0
インスタンス: () => T
)も生成できます。 これを行うには、戻り値を生成するジェネレーターをGen.function0
ます。
val f = Gen.function0(Arbitrary.arbitrary[Int]).sample // tostring , - : // some(org.scalacheck.gen$$$lambda$40/1418621776@4f7d0008)
コンテナ発電機
arbitrary
を使用して、標準コレクションのインスタンスを生成できます。
import Arbitrary.arbitrary val listgen = arbitrary[List[Int]] val optgen = arbitrary[Option[Int]]
しかし、これは多くの場合十分ではないため、ScalaCheckはコレクションとその生成を操作するための高度なツールを提供します。 次に、 sample
メソッドによって返されるOption
を省略し、誤解を招かないように、すぐに値を表示します。
生成オプション
Gen.some
を使用すると、「収入」が保証されます。
// : Gen.some("") // Some()
さらに悪いことに、お金に追加の自由度がある場合:
// . Gen.option("") // None
リストと辞書を生成します
Gen.listOf
は任意の長さのリストを生成し、 Gen.listOfN
は指定された長さのリストを生成します。
// 3 Gen[Int]. Gen.listOf(3) map (_ take 5) // List(3, 3, 3, 3, 3) // . Gen.listOfN(5, Gen.posNum[Double]) map (_ take 5)
Gen.listOf
ジェネレーターは空のリストを返す場合があります。 空でないリストが必要であることが保証されている場合は、 Gen.nonEmptyListOf
を使用できます。 辞書またはマップ( Map
)を生成するには、同様のメソッドがあります: Gen.mapOf
、 Gen.mapOfN
、およびGen.nonEmptyMapOf
です。 リストメソッドとは異なり、辞書ジェネレーターには、2要素のタプルジェネレーターが必要です。
import Arbitrary._ val tupleGen = for { i <- arbitrary[Short] j <- arbitrary[Short] } yield (i, j) Gen.mapOfN(3, tupleGen) map (_ take 3) // Map(10410 -> -7991, -19269 -> -18509, 0 -> -18730)
特定のセットの要素のリストを生成します
Gen.someOf
は、指定したセットに含まれる特定の要素セットからArrayBuffer
を返します。次に例を示します。
import org.scalacheck.Gen.someOf val numbers = someOf(List(1, 2, 3, 4, 5)).sample // Some(ArrayBuffer(5, 4, 3)) val leds = someOf(List(Red, Green, Blue)).sample // Some(ArrayBuffer(Blue, Green))
Gen.pick
と同様にGen.someOf
ます。 唯一の違いは、 Gen.pick
使用すると、必要な要素数を指定できることです。
import org.scalacheck.Gen.pick val lettersGen = Gen.pick(2, List('a', 'e', 'i', 'o', 't', 'n', 'm')).sample // Some(ArrayBuffer(m, n))
シーケンスを生成する
Gen.sequence
は、受け入れられた値のシーケンスに基づいてArrayList
を作成します。 提供されたジェネレーターの少なくとも1つが該当する場合、シーケンス全体が該当します。
import Gen.{const, choose} val ArrayListGen = Gen.sequence(List(const('a'), const('b'), choose('c', 'd'))) // [a, b, d]
無限のストリームを生成します
ScalaCheckは、無限ストリームを生成する機能をサポートしています。
val stream = Gen.infiniteStream(Gen.choose(0,1)) // 8 stream.sample map (stream => stream.take(8).toList) // Some(List(1, 0, 0, 0, 1, 1, 1, 0))
コンテナ発電機
辞書とリストの上記のメソッドは、実際には、 Gen.containerOf
とそのパートナーを使用する特殊なケースです: containerOfN
とnonEmptyContainerOf`。 これは、私たちが知っているほとんどのコレクションを生成できる最も一般的な形式です。 次の例を見てみましょう。
import Arbitrary._ // «» . val prettyShortSeq = Gen.containerOfN[Seq, Short](3, arbitrary[Short]) // Vector(0, -24114, 32767) // : . val genNonEmptyList = Gen.nonEmptyContainerOf[Map[K, V], (K, V)] (oneOf("foo", "bar"))
containerOf
作成されたジェネレーターは、型ではなく抽象型で作成されます。 containerOf
メソッドのシグネチャを見てみましょう。
def containerOf[C[_],T](g: Gen[T])(implicit evb: Buildable[T,C[T]], evt: C[T] => Traversable[T] ): Gen[C[T]] =
そしてその実装について:
buildableOf[C[T],T](g)
したがって、リストまたは* ⟶ *
として表すことができる何かを生成する必要がある場合、 containerOf
が役立ちます。 そして、 * ⟶ * ⟶ *
ようなもの(たとえばMap
)を生成する必要がある場合は、 * ⟶ * ⟶ *
実装で見ることができるさらに抽象的なメソッドを使用する必要があります。
def mapOf[T,U](g: => Gen[(T,U)]) = buildableOf[Map[T,U],(T,U)](g)
container
メソッドと同様に、メソッドnonEmptyBuildableOf
、 nonEmptyBuildableOf
およびnonEmptyBuildableOf
ます。 通常、Scalacheckは、渡されたコレクションを型として構築する方法を決定します。 これを行うために、タイプorg.scalacheck.util.Buildable
暗黙的なインスタンスを使用します。 このようなインスタンスは、すべての標準コレクションタイプのScalaCheckで宣言されます。 Arbitrary
インスタンスと同様に、コレクションを実装してcontainerOf
サポートを取得するのは簡単です。
高い発電機
親愛なる読者の皆さん、 高階の機能は何か知っていると思います。 ジェネレーターにも同様の動作があります。ジェネレーターは、他のジェネレーターを引数として受け取り、他のジェネレーターを生成できます。 まれに例外を除いて、以前に検討したコンテナージェネレーターも高次ジェネレーターです。
多くの一つ
Gen.oneOf
は2からN
ジェネレーターを受け取ることができ、それによって新しいジェネレーターを生成します。
import Gen.{oneOf, const, choose} // Gen[T] val dogies = oneOf("Lucy", "Max", "Daisy", "Barney") // val windows = oneOf(choose(1, 8), const(10))
oneOf
、リストなどのscala.collection.Seq
渡すことができます。
周波数発生器
Gen.frequency
は、入力としてタプルのリストを受け入れます。 それぞれがジェネレーターとその確率的重みで構成されます。 出力は、渡された整数値を確率の重みとして使用する新しいジェネレーターです。
val russianLettersInText = Gen.frequency ( (9, ''), (9, ''), (8, ''), (7, ''), (6, '') //.. )
レイジージェネレーター
Gen.lzy
を使用すると、本当に必要になるまで生成が遅れます。 合成ジェネレーターや、ASTなどの再帰的なデータ構造の生成に非常に役立ちます。 Gen.lzy
を使用する場合、計算は1回実行されます。 Gen.delay
計算もGen.delay
が、毎回実行されます。
発電機サイズ
Gen.size
は、唯一のパラメーターとしてラムダを取り、パラメーターとして整数を取ります。 この数値は、ScalaCheckが実行時に示すサイズです。 Gen.resize
使用Gen.resize
と、必要に応じてジェネレーターのサイズを設定できGen.resize
。 これを次の例で説明します。
def genNonEmptySeq[T](genElem: Gen[T]): Gen[Seq[T]] = Gen.sized { size => for { listSize <- Gen.choose(1, size) list <- Gen.containerOfN[Seq, T](listSize, genElem) } yield list } val intVector = genNonEmptySeq(Arbitrary.arbitrary[Int]) val p = Prop.forAll(Gen.resize(5, intVector)) { list => list.length <= 5 }
チェック:
p.check // + OK, passed 100 tests.
Gen.resize
は、既存のGen.resize
れたものに基づいて新しいジェネレーターを作成しGen.resize
。 これは、再帰ジェネレーターを作成するとき、および合成を使用して単純なジェネレーターから複雑なジェネレーターを作成するときに便利です。
フラクタルジェネレータ
多くの場合、再帰的なデータ構造、つまりASTを使用する必要があります。 . , , , . :
trait IntTree case class Leaf (value: Int) extends IntTree case class Node (children: Seq[IntTree]) extends IntTree
:
import org.scalacheck.Gen._ def treeGen: Gen[IntTree] = oneOf(leafGen, nodeGen) def leafGen: Gen[Leaf] = arbitrary[Int] map (value => Leaf(value)) def nodeGen: Gen[Node] = listOf(treeGen) map (children => Node(children))
:
treeGen.sample Exception in thread "main" java.lang.StackOverflowError at org.scalacheck.ArbitraryLowPriority$$anon$1.arbitrary(Arbitrary.scala:70) at org.scalacheck.ArbitraryLowPriority.arbitrary(Arbitrary.scala:74) at org.scalacheck.ArbitraryLowPriority.arbitrary$(Arbitrary.scala:74) at org.scalacheck.Arbitrary$.arbitrary(Arbitrary.scala:61)
treeGen
. . . treeGen
:
def treeGen: Gen[IntTree] = lzy(oneOf(leafGen, nodeGen))
:
treeGen.sample // Some(Leaf(857998833)) // Some(Leaf(2147483647)) // Some(Leaf(489549235))
Exception in thread "main" java.lang.StackOverflowError at org.scalacheck.rng.Seed.next(Seed.scala:17) at org.scalacheck.rng.Seed.long(Seed.scala:39) at org.scalacheck.Gen$Choose$.org$scalacheck$Gen$Choose$$chLng(Gen.scala:309) at org.scalacheck.Gen$Choose$$anon$5.$anonfun$choose$1(Gen.scala:358) at org.scalacheck.Gen$$anon$3.doApply(Gen.scala:254) at org.scalacheck.Gen.$anonfun$map$1(Gen.scala:75) at org.scalacheck.Gen$$anon$3.doApply(Gen.scala:254) at org.scalacheck.Gen.$anonfun$flatMap$2(Gen.scala:80) at org.scalacheck.Gen$R.flatMap(Gen.scala:242) at org.scalacheck.Gen$R.flatMap$(Gen.scala:239)
, , . . Gen.sized
Gen.resize
:
def nodeGen: Gen[Node] = sized { size => choose(0, size) flatMap { currSize => val nGen = resize(size / (currSize + 1), treeGen) listOfN(currSize, nGen) map (child => Node(child)) } }
:
val nGen = resize(size / (currSize + 1), treeGen)
より小さなサイズの新しいジェネレーターを作成します:ネストのレベルに比例してサイズが縮小されます。特定のNに対してサイズ0の空のジェネレーターが作成され、スタックオーバーフローは発生しません。ジェネレーターは、ネストのレベルがわからないため、毎回ジェネレーターのサイズを小さくする必要があります。
そこで、再帰的なジェネレーターを見つけました。JSONのAST生成の例は、Eric Torreborre(specs2の作成者)ブログにあります。
これで、ジェネレーターとの関係を終了し、プロパティに進みます。これらの詳細については、シリーズの次の記事で説明します。じゃあね!