A blog exploring advanced programming topics in Swift.

Open Sourcing Gen

Monday Mar 18, 2019


Today we are open sourcing Gen: a lightweight wrapper around Swift’s randomness API’s that makes randomness more composable, transformable and controllable!


We are excited to announce the 0.1.0 release of Gen, a new API for expressing randomness in Swift. Its focus is on composability (combining multiple forms of randomness into new forms of randomness), transformability (applying functions to randomness), and controllability (deterministic pseudo-randomness for times we need it). With these three features you can break down large, complex forms of randomness into smaller, simpler pieces, and you can write tests for it!

Motivation

Swift’s randomness API is powerful and simple to use. It allows us to create random values from many basic types, such as booleans and numeric types, and it allows us to randomly shuffle arrays and pluck random elements from collections.

However, it does not make it easy for us to extend the randomness API. For example, while it may gives us ways of generating random booleans, numeric values, and even ways to shuffle arrays and pluck random elements from arrays, it says nothing about creating random strings, random collections of values, or random values from our own data types.

Further, the API is not very composable, which would allow us to create complex types of randomness from simpler pieces. One primarily uses the API by calling static random functions on types, such as Int.random(in: 0...9), but there is no guidance on how to generate new types of randomness from existing randomness.

Gen

Gen is a lightweight wrapper over Swift’s randomness APIs that makes it easy to build custom generators of any kind of value. Most often you will reach for one of the static variables inside Gen to get access to a Gen value:

Gen.bool // Gen<Bool>

Rather than immediately producing a random value, Gen describes a random value that can be produced by calling its run method:

let myGen = Gen.bool // Gen<Bool>

myGen.run() // true
myGen.run() // true
myGen.run() // false

Every random function that comes with Swift is also available as a static function on Gen:

// Swift's API
Int.random(in: 0...9) // 4

// Gen's API
Gen.int(in: 0...9).run() // 6

The reason it is powerful to wrap randomness in the Gen type is that we can make the Gen type composable. For example, a generator of integers can be turned into a generator of numeric strings with a simple application of the map function:

let digit = Gen.int(in: 0...9)           // Gen<Int>
let stringDigit = digit.map(String.init) // Gen<String>

stringDigit.run() // "7"
stringDigit.run() // "1"
stringDigit.run() // "3"

Already this is a form of randomness that Swift’s API’s do not provide out of the box.

Gen provides many operators for generating new types of randomness, such as map, flatMap and zip, as well as helper functions for generating random arrays, sets, dictionaries, string, distributions and more! A random password generator, for example, is just a few operators away.

// Take a generator of random letters and numbers.
let password = Gen.letterOrNumber
  // Generate 6-character strings of them.
  .string(of: .always(6))
  // Generate 3 segments of these strings.
  .array(of: .always(3))
  // And join them.
  .map { $0.joined(separator: "-") }

password.run() // "9BiGYA-fmvsOf-VYDtDv"
password.run() // "dS2MGr-FQSuC4-ZLEicl"
password.run() // "YusZGF-HILrCo-rNGfCA"

But composability isn’t the only reason the Gen type shines. By delaying the creation of random values until the run method is invoked, we allow ourselves to control randomness in circumstances where we need determinism, such as tests. The run method has an overload that takes a RandomNumberGenerator value, which is Swift’s protocol that powers their randomness API. By default it uses the SystemRandomNumberGenerator, which is a good source of randomness, but we can also provide a seedable “pseudo” random number generator, so that we can get predictable results in tests:

var lcrng = LCRNG(seed: 0)
Gen.int(in: 0...9).run(using: &lcrng) // "8"
Gen.int(in: 0...9).run(using: &lcrng) // "1"
Gen.int(in: 0...9).run(using: &lcrng) // "7"

lcrng.seed = 0
Gen.int(in: 0...9).run(using: &lcrng) // "8"
Gen.int(in: 0...9).run(using: &lcrng) // "1"
Gen.int(in: 0...9).run(using: &lcrng) // "7"

This means you don’t have to sacrifice testability when leveraging randomness in your application.

Learn more

The Gen type has been explored on Point-Free numerous times. We began by showing that randomness can be made composable by expressiong it as a function. This allowed us to define map, flatMap and zip operations on randomness, which helped us create very complex forms of randomness for just a few small, simple pieces.

In order to show just how powerful composable randomness is, we wrote a blog post demonstrating how to create a Zalgo text generator. This consisted of defining small generators that do a specific thing, such as generating special unicode characters, and the piecing them together to finally give us the generator that allows us to create bizarre strings such as: P̵̙̬̬̝̹̰̜ͧ̿o̎ĩͪͪ͗n͓̪̝̓t̊̏̾̊̆-̦̲̥͉F̠͖͈̮̾́ͨ͐͝r̸͋̆̅̅ͪ̚ë̝͑ͣ̒̏̈́̉e̟̺̪͕̹͆ͩͯ̑ͣ͂̉.

Then we showed how randomness can be made controllable (part 1 and part 2) by slightly tweaking Gen definition so that it took a RandomNumberGenerator, which is the Swift protocol that powers all of Swift’s randomness API’s. This allowed us to keep all of Gen‘s nice compositional properties while also allowing us to plug in our own random number generators. In particular, we can use a deterministic, seedable, pseudo-random number generator in tests so that we can still test code that invokes randomness API’s.

Try it out today!

The official 0.1.0 release of Gen is on GitHub now, and we have more improvements and refinements coming soon. We hope that Gen will help you control the complexity in your applications that arises from randomness, both by making the randomness simpler to understand and easier to test.


Subscribe to Point-Free

👋 Hey there! If you got this far, then you must have enjoyed this post. You may want to also check out Point-Free, a video series covering advanced programming topics in Swift. Consider subscribing today!