0
# Function Generation
1
2
ScalaCheck's Cogen (co-generator) framework enables the generation of functions for testing higher-order functions and functional programming patterns. Co-generators work by using input values to perturb random seeds, creating deterministic function generation based on the input space.
3
4
## Capabilities
5
6
### Core Cogen Trait
7
8
The fundamental co-generator abstraction that perturbs random seeds based on input values.
9
10
```scala { .api }
11
sealed trait Cogen[T] {
12
def perturb(seed: Seed, t: T): Seed
13
def cogen[A](t: T, g: Gen[A]): Gen[A]
14
def contramap[S](f: S => T): Cogen[S]
15
}
16
17
object Cogen {
18
def apply[T](implicit ev: Cogen[T]): Cogen[T]
19
def apply[T](f: T => Long): Cogen[T]
20
def apply[T](f: (Seed, T) => Seed): Cogen[T]
21
def perturb[T](seed: Seed, t: T)(implicit c: Cogen[T]): Seed
22
}
23
```
24
25
**Usage Examples:**
26
```scala
27
// Custom co-generator from simple function
28
case class UserId(id: Long)
29
implicit val cogenUserId: Cogen[UserId] = Cogen(_.id)
30
31
// Co-generator from seed transformation
32
case class Coordinate(x: Double, y: Double)
33
implicit val cogenCoordinate: Cogen[Coordinate] = Cogen { (seed, coord) =>
34
seed.reseed(coord.x.toLong).reseed(coord.y.toLong)
35
}
36
37
// Use co-generator to create deterministic perturbation
38
val seed = Seed.random()
39
val perturbedSeed = Cogen.perturb(seed, UserId(42))
40
```
41
42
### Function Generation Factory Methods
43
44
Utilities for creating co-generators from various function types.
45
46
```scala { .api }
47
def it[T, U](f: T => Iterator[U])(implicit U: Cogen[U]): Cogen[T]
48
def perturbPair[A, B](seed: Seed, ab: (A, B))(implicit ca: Cogen[A], cb: Cogen[B]): Seed
49
def perturbArray[A](seed: Seed, as: Array[A])(implicit ca: Cogen[A]): Seed
50
def domainOf[A, B](f: A => B)(implicit cb: Cogen[B]): Cogen[A]
51
```
52
53
**Usage Examples:**
54
```scala
55
// Co-generator from iterator function
56
case class WordList(words: List[String])
57
implicit val cogenWordList: Cogen[WordList] = Cogen.it(_.words.iterator)
58
59
// Co-generator from function composition
60
val stringLengthCogen: Cogen[String] = Cogen.domainOf((s: String) => s.length)
61
```
62
63
### Primitive Type Co-generators
64
65
Co-generators for basic Scala and Java primitive types.
66
67
```scala { .api }
68
implicit val cogenUnit: Cogen[Unit]
69
implicit val cogenBoolean: Cogen[Boolean]
70
implicit val cogenByte: Cogen[Byte]
71
implicit val cogenShort: Cogen[Short]
72
implicit val cogenChar: Cogen[Char]
73
implicit val cogenInt: Cogen[Int]
74
implicit val cogenLong: Cogen[Long]
75
implicit val cogenFloat: Cogen[Float]
76
implicit val cogenDouble: Cogen[Double]
77
```
78
79
**Usage Examples:**
80
```scala
81
// Generate functions using primitive co-generators
82
val intToStringFuncs: Gen[Int => String] = Gen.function1(Arbitrary.arbitrary[String])
83
84
val boolPredicates: Gen[Double => Boolean] = Gen.function1(Arbitrary.arbitrary[Boolean])
85
86
// Test higher-order functions
87
val mapProp = forAll { (f: Int => String, list: List[Int]) =>
88
list.map(f).length == list.length
89
}
90
```
91
92
### Numeric Type Co-generators
93
94
Co-generators for big numeric types and mathematical structures.
95
96
```scala { .api }
97
implicit val cogenBigInt: Cogen[BigInt]
98
implicit val cogenBigDecimal: Cogen[BigDecimal]
99
```
100
101
**Usage Examples:**
102
```scala
103
val bigIntFunctions: Gen[BigInt => Boolean] = Gen.function1(Gen.prob(0.5))
104
105
val bigDecimalTransforms: Gen[BigDecimal => BigDecimal] =
106
Gen.function1(Arbitrary.arbitrary[BigDecimal])
107
```
108
109
### String and Symbol Co-generators
110
111
Co-generators for textual and symbolic types.
112
113
```scala { .api }
114
implicit val cogenString: Cogen[String]
115
implicit val cogenSymbol: Cogen[Symbol]
116
```
117
118
**Usage Examples:**
119
```scala
120
// Generate string processing functions
121
val stringProcessors: Gen[String => Int] = Gen.function1(Arbitrary.arbitrary[Int])
122
123
val symbolMappers: Gen[Symbol => String] = Gen.function1(Gen.alphaStr)
124
125
// Test string operations
126
val stringConcatProp = forAll { (f: String => String, s: String) =>
127
val result = f(s)
128
result.length >= 0 // Functions can return any string
129
}
130
```
131
132
### Collection Type Co-generators
133
134
Co-generators for various collection types, enabling function generation over collections.
135
136
```scala { .api }
137
implicit val cogenBitSet: Cogen[BitSet]
138
implicit def cogenArray[A](implicit ca: Cogen[A]): Cogen[Array[A]]
139
implicit def cogenList[A](implicit ca: Cogen[A]): Cogen[List[A]]
140
implicit def cogenVector[A](implicit ca: Cogen[A]): Cogen[Vector[A]]
141
implicit def cogenStream[A](implicit ca: Cogen[A]): Cogen[Stream[A]]
142
implicit def cogenSeq[CC[x] <: Seq[x], A](implicit ca: Cogen[A]): Cogen[CC[A]]
143
implicit def cogenSet[A](implicit ca: Cogen[A], ord: Ordering[A]): Cogen[Set[A]]
144
implicit def cogenSortedSet[A](implicit ca: Cogen[A]): Cogen[SortedSet[A]]
145
implicit def cogenMap[K, V](implicit ck: Cogen[K], cv: Cogen[V], ord: Ordering[K]): Cogen[Map[K, V]]
146
implicit def cogenSortedMap[K, V](implicit ck: Cogen[K], cv: Cogen[V]): Cogen[SortedMap[K, V]]
147
```
148
149
**Usage Examples:**
150
```scala
151
// Generate functions over collections
152
val listProcessors: Gen[List[Int] => Boolean] = Gen.function1(Gen.prob(0.3))
153
154
val mapTransformers: Gen[Map[String, Int] => Int] = Gen.function1(Gen.posNum[Int])
155
156
// Test collection operations
157
val filterProp = forAll { (predicate: Int => Boolean, list: List[Int]) =>
158
val filtered = list.filter(predicate)
159
filtered.length <= list.length
160
}
161
162
val mapProp = forAll { (transform: String => Int, map: Map[String, String]) =>
163
val transformed = map.mapValues(transform)
164
transformed.keySet == map.keySet
165
}
166
```
167
168
### Higher-Order Type Co-generators
169
170
Co-generators for Option, Either, Try, and other wrapper types.
171
172
```scala { .api }
173
implicit def cogenOption[A](implicit ca: Cogen[A]): Cogen[Option[A]]
174
implicit def cogenEither[A, B](implicit ca: Cogen[A], cb: Cogen[B]): Cogen[Either[A, B]]
175
implicit def cogenTry[A](implicit ca: Cogen[A]): Cogen[Try[A]]
176
```
177
178
**Usage Examples:**
179
```scala
180
// Generate functions over optional values
181
val optionProcessors: Gen[Option[String] => Int] = Gen.function1(Gen.choose(0, 100))
182
183
val eitherHandlers: Gen[Either[String, Int] => Boolean] = Gen.function1(Gen.prob(0.5))
184
185
// Test optional operations
186
val optionMapProp = forAll { (f: String => Int, opt: Option[String]) =>
187
opt.map(f).isDefined == opt.isDefined
188
}
189
190
val eitherFoldProp = forAll { (left: String => Int, right: Int => Int, e: Either[String, Int]) =>
191
val result = e.fold(left, right)
192
result >= 0 || result < 0 // Any integer is valid
193
}
194
```
195
196
### Function Type Co-generators
197
198
Co-generators for function values and partial functions.
199
200
```scala { .api }
201
implicit def cogenFunction0[Z](implicit cz: Cogen[Z]): Cogen[() => Z]
202
implicit def cogenPartialFunction[A, B](
203
implicit aa: Arbitrary[A],
204
cb: Cogen[B]
205
): Cogen[PartialFunction[A, B]]
206
```
207
208
**Usage Examples:**
209
```scala
210
// Generate higher-order functions
211
val thunkProcessors: Gen[(() => Int) => String] = Gen.function1(Gen.alphaStr)
212
213
val partialFunctionCombiners: Gen[PartialFunction[Int, String] => Boolean] =
214
Gen.function1(Gen.prob(0.6))
215
216
// Test function composition
217
val compositionProp = forAll { (f: Int => String, g: String => Boolean, x: Int) =>
218
val composed = g.compose(f)
219
composed(x) == g(f(x))
220
}
221
```
222
223
### Exception and Error Co-generators
224
225
Co-generators for exception hierarchy types.
226
227
```scala { .api }
228
implicit val cogenException: Cogen[Exception]
229
implicit val cogenThrowable: Cogen[Throwable]
230
```
231
232
**Usage Examples:**
233
```scala
234
val exceptionHandlers: Gen[Exception => String] = Gen.function1(Gen.alphaStr)
235
236
val errorProcessors: Gen[Throwable => Int] = Gen.function1(Gen.choose(-100, 100))
237
238
// Test exception handling
239
val catchProp = forAll { (handler: Exception => String, ex: Exception) =>
240
try {
241
throw ex
242
} catch {
243
case e: Exception => handler(e).length >= 0
244
}
245
}
246
```
247
248
### Duration and Time Co-generators
249
250
Co-generators for temporal types.
251
252
```scala { .api }
253
implicit val cogenFiniteDuration: Cogen[FiniteDuration]
254
implicit val cogenDuration: Cogen[Duration]
255
```
256
257
**Usage Examples:**
258
```scala
259
val durationTransforms: Gen[FiniteDuration => Long] = Gen.function1(Gen.posNum[Long])
260
261
val timeoutHandlers: Gen[Duration => Boolean] = Gen.function1(Gen.prob(0.4))
262
263
// Test duration operations
264
val durationProp = forAll { (f: FiniteDuration => Long, d: FiniteDuration) =>
265
val result = f(d)
266
result >= Long.MinValue && result <= Long.MaxValue
267
}
268
```
269
270
### UUID Co-generator
271
272
Co-generator for universally unique identifiers.
273
274
```scala { .api }
275
implicit val cogenUUID: Cogen[UUID]
276
```
277
278
**Usage Examples:**
279
```scala
280
val uuidProcessors: Gen[UUID => String] = Gen.function1(Gen.alphaNumStr)
281
282
val uuidPredicates: Gen[UUID => Boolean] = Gen.function1(Gen.prob(0.5))
283
```
284
285
## Function Generation Patterns
286
287
### Testing Higher-Order Functions
288
289
```scala
290
// Test map operation on lists
291
val mapProperty = forAll { (f: Int => String, list: List[Int]) =>
292
val mapped = list.map(f)
293
mapped.length == list.length &&
294
(list.isEmpty ==> mapped.isEmpty) &&
295
(list.nonEmpty ==> mapped.nonEmpty)
296
}
297
298
// Test filter operation
299
val filterProperty = forAll { (predicate: String => Boolean, list: List[String]) =>
300
val filtered = list.filter(predicate)
301
filtered.length <= list.length &&
302
filtered.forall(predicate) &&
303
filtered.forall(list.contains)
304
}
305
306
// Test fold operations
307
val foldProperty = forAll { (f: (Int, String) => Int, zero: Int, list: List[String]) =>
308
val result = list.foldLeft(zero)(f)
309
list.isEmpty ==> (result == zero)
310
}
311
```
312
313
### Function Composition Testing
314
315
```scala
316
val compositionProperty = forAll {
317
(f: Int => String, g: String => Boolean, h: Boolean => Double, x: Int) =>
318
319
val composed = h.compose(g).compose(f)
320
val stepwise = h(g(f(x)))
321
322
composed(x) == stepwise
323
}
324
325
val andThenProperty = forAll { (f: Int => String, g: String => Boolean, x: Int) =>
326
(f andThen g)(x) == g(f(x))
327
}
328
```
329
330
### Partial Function Testing
331
332
```scala
333
val partialFunctionProperty = forAll {
334
(pf: PartialFunction[Int, String], x: Int) =>
335
336
pf.isDefinedAt(x) ==> {
337
val result = pf(x)
338
result != null // Partial functions should not return null when defined
339
}
340
}
341
342
val liftProperty = forAll { (pf: PartialFunction[String, Int], s: String) =>
343
val lifted = pf.lift
344
pf.isDefinedAt(s) ==> lifted(s).isDefined
345
}
346
```
347
348
### Custom Co-generator Creation
349
350
```scala
351
// Complex custom type with co-generator
352
case class Person(name: String, age: Int, emails: List[String])
353
354
implicit val cogenPerson: Cogen[Person] = Cogen { (seed, person) =>
355
val seed1 = Cogen.perturb(seed, person.name)
356
val seed2 = Cogen.perturb(seed1, person.age)
357
Cogen.perturb(seed2, person.emails)
358
}
359
360
// Test functions over custom types
361
val personProperty = forAll { (f: Person => Boolean, p: Person) =>
362
val result = f(p)
363
result == true || result == false // Boolean functions return booleans
364
}
365
366
// Recursive data structure co-generator
367
sealed trait Tree[+A]
368
case class Leaf[A](value: A) extends Tree[A]
369
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
370
371
implicit def cogenTree[A](implicit ca: Cogen[A]): Cogen[Tree[A]] = Cogen {
372
case Leaf(value) => Cogen { (seed, _) => Cogen.perturb(seed, value) }
373
case Branch(left, right) => Cogen { (seed, _) =>
374
val seed1 = cogenTree[A].perturb(seed, left)
375
cogenTree[A].perturb(seed1, right)
376
}
377
}.perturb
378
```
379
380
### Function Generation with Constraints
381
382
```scala
383
// Generate only pure functions (deterministic)
384
def pureFunction[A, B](implicit ca: Cogen[A], ab: Arbitrary[B]): Gen[A => B] = {
385
Gen.function1[A, B](ab.arbitrary)(ca, ab)
386
}
387
388
// Generate functions with specific properties
389
def monotonicIntFunction: Gen[Int => Int] = {
390
// This is a simplified example - real monotonic function generation is complex
391
Gen.function1[Int, Int](Gen.choose(0, 1000))
392
.suchThat { f =>
393
// Test monotonicity on small sample
394
val sample = (1 to 10).toList
395
sample.zip(sample.tail).forall { case (x, y) => f(x) <= f(y) }
396
}
397
}
398
399
// Generate bijective functions (for small domains)
400
def bijectiveFunction[A](domain: List[A])(implicit ca: Cogen[A]): Gen[A => A] = {
401
Gen.oneOf(domain.permutations.toSeq).map { permutation =>
402
val mapping = domain.zip(permutation).toMap
403
(a: A) => mapping.getOrElse(a, a)
404
}
405
}
406
```
407
408
## Advanced Function Generation
409
410
### Stateful Function Generation
411
412
```scala
413
// Generate functions that maintain internal state
414
case class Counter(initial: Int) {
415
private var count = initial
416
def increment(): Int = { count += 1; count }
417
def current: Int = count
418
}
419
420
val statefulProperty = forAll { (init: Int) =>
421
val counter = Counter(init)
422
val first = counter.increment()
423
val second = counter.increment()
424
425
first == init + 1 && second == init + 2
426
}
427
```
428
429
### Function Generation for Testing Frameworks
430
431
```scala
432
// Generate test predicates
433
val predicateProperty = forAll { (predicate: List[Int] => Boolean, lists: List[List[Int]]) =>
434
val results = lists.map(predicate)
435
results.forall(r => r == true || r == false) // All results are booleans
436
}
437
438
// Generate validation functions
439
case class ValidationResult(isValid: Boolean, errors: List[String])
440
441
val validatorProperty = forAll {
442
(validator: String => ValidationResult, inputs: List[String]) =>
443
444
val results = inputs.map(validator)
445
results.forall(r => r.isValid || r.errors.nonEmpty) // Invalid results have errors
446
}
447
```