type-aligned list generation
This document provides examples of how you can generate pseudorandom (but deterministic) type-aligned lists using maligned and Scalacheck.
prerequisites
The code examples assume that the following items have been imported:
import com.salesforce.maligned._
import com.salesforce.maligned.gen.TAListGen._
import cats.data.{Chain, Kleisli, Writer}
import cats.implicits._
import org.scalacheck.Arbitrary.arbitrary
import org.scalacheck.Gen
import org.scalacheck.rng.Seed
They also depend on the following helper method to sample a Scalacheck Gen
instance with the provided RNG seed:
def sample[A](gen: Gen[A], seed: Seed): A = gen.apply(Gen.Parameters.default, seed).get
generating random type-aligned lists of functions
Importing TAListGen._
provides implicit Scalacheck Arbitrary
instances for type-aligned lists (TANonEmptyList
and TAList
) of function (Function1
) elements.
For example, we can create a generator for lists of length 1 to 6 with an input type of Int
and an output type of Option[Double]
:
val taListGen: Gen[TANonEmptyList[Function1, Int, Option[Double]]] =
Gen.resize(6, arbitrary[TANonEmptyList[Function1, Int, Option[Double]]])
Let’s go ahead and sample a random list from this generator:
val exampleList: TANonEmptyList[Function1, Int, Option[Double]] = sample(taListGen, Seed(13L))
We can compose this list into a single function:
val f: Int => Option[Double] = exampleList.composeAll
f(3) should ===(Some(-8.952315155229148e-202))
But this doesn’t give us much insight into the internal structure of the list. Is it just a single element that is a function from Int
to Option[Double]
? We can see that it’s not by checking the length of the list:
exampleList.toList.size should ===(6)
To get a better understanding of the composition of the list, we’ll have to get a little fancier. We can wrap each function element in some logic to log its output. This will allow us to see all of the intermediate values of the composed functions.
val loggingFn1 = new FunctionK2[Function1, Kleisli[Writer[Chain[String], *], *, *]] {
override def apply[A, B](f: A => B): Kleisli[Writer[Chain[String], *], A, B] =
Kleisli { a =>
val output = f(a)
Writer(Chain(s"output: ${output.toString}"), output)
}
}
val exampleLoggedList
: TANonEmptyList[Kleisli[Writer[Chain[String], *], *, *], Int, Option[Double]] =
exampleList.mapK(loggingFn1)
val fnWithLogging = exampleLoggedList.composeAll
fnWithLogging(3).run should ===(
(
Chain(
"output: -1",
"output: -1809610947",
"output: List(-1, 1809634624, 2147483647, -53758633)",
"output: -4249568764532995907",
"output: -9223372036854775808",
"output: Some(-8.952315155229148E-202)"
),
Some(-8.952315155229148e-202)))
As you can see, the list was composed of functions of various types. Because the RNG is deterministic, if we run the function again with the same input value, we’ll get the same output. But if we change the input value, we’ll see completely different intermediate values.
fnWithLogging(3).run should ===(
(
Chain(
"output: -1",
"output: -1809610947",
"output: List(-1, 1809634624, 2147483647, -53758633)",
"output: -4249568764532995907",
"output: -9223372036854775808",
"output: Some(-8.952315155229148E-202)"
),
Some(-8.952315155229148e-202)))
fnWithLogging(4).run should ===(
(
Chain(
"output: 1929283752716877934",
"output: 1",
"output: List(160958044, 1601040997, -2147483648, -139952998, 1)",
"output: 7163398877903029397",
"output: 7118195307056659999",
"output: Some(1.0051322294659681E109)"
),
Some(1.0051322294659681e109)))
If we generate a new list, it will be comprised of completely different functions (with different inner input/output types):
val exampleList2: TANonEmptyList[Function1, Int, Option[Double]] =
sample(taListGen, Seed(1000L))
val exampleLoggedList2
: TANonEmptyList[Kleisli[Writer[Chain[String], *], *, *], Int, Option[Double]] =
exampleList2.mapK(loggingFn1)
val fn2WithLogging = exampleLoggedList2.composeAll
fn2WithLogging(3).run should ===(
(
Chain(
"output: 8106783551773268132",
"output: 0",
"output: 㷱襈鯡䐌",
"output: Left(false)",
"output: Some(9.600598437719758E264)"),
Some(9.600598437719758e264)))