Skip to content

Instantly share code, notes, and snippets.

@nrinaudo
Created February 7, 2019 20:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nrinaudo/838731cabed453bf73fd8160b2147d9b to your computer and use it in GitHub Desktop.
Save nrinaudo/838731cabed453bf73fd8160b2147d9b to your computer and use it in GitHub Desktop.
Using optics to patch `Gen` instances
import monocle.macros._
import org.scalacheck._
import scalaz.scalacheck.ScalaCheckBinding._
// Deep hierarchy of product types.
case class Document(author: Author)
case class Author(firstName: String, lastName: String, city: City)
case class City(name: String, country: Country)
case class Country(name: String, continent: Continent)
case class Continent(name: String)
object GenLenses {
// (Stupidly) simple `Gen` instance
val genDoc: Gen[Document] = for {
firstName <- Gen.identifier
lastName <- Gen.identifier
city <- Gen.identifier
country <- Gen.identifier
continent <- Gen.identifier
} yield Document(Author(firstName, lastName, City(city, Country(country, Continent(continent)))))
// This is how you modify the way the continent's name is generated.
val noLens = genDoc.flatMap(doc => doc.copy(
author = doc.author.copy(
city = doc.author.city.copy(
country = doc.author.city.country.copy(
continent = doc.author.city.country.continent.copy(
name = doc.author.city.country.continent.name.toUpperCase
)
)
)
)
))
// Same, but with lenses.
val continentNameLens = GenLens[Document](_.author.city.country.continent.name)
val withLens = genDoc.map(continentNameLens.modify(_.toUpperCase))
// This is how you replace the `Gen` used for the continent name using lenses.
val genContinentName = Gen.numStr
genDoc.flatMap(doc => genContinentName.map(name => continentNameLens.set(name)(doc)))
// Same thing, slightly terser.
genDoc.flatMap(continentNameLens.modifyF(_ => genContinentName))
}
@chwthewke
Copy link

If you want to instead "perturb" the continent generator inside, you might write:

genDoc.flatMap( continentNameLens.modifyF( c => Gen.oneOf( "North", "South" ).map( _ + " " + c ) ) )

whereas the modify-restricted approach would be:

genDoc.flatMap( Gen.oneOf( "North", "South" ).map( q => continentNameLens.modify( q + " " + _ ) ) )

... well, I expected that second one to be longer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment