Code Monkey home page Code Monkey logo

swift-gen's Introduction

๐ŸŽฑ Gen

CI

Composable, transformable, controllable randomness.

Table of Contents

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, nor does it provide an API that is composable, which would allow us to create complex types of randomness from simpler pieces.

Gen is a lightweight wrapper over Swift's randomness APIs that makes it easy to build custom generators of any kind of value.

Examples

Gen's namesake type, Gen, is responsible for producing random values. 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 Gen's API
Int.random(in: 0...9) Gen.int(in: 0...9)
Double.random(in: 0...9) Gen.double(in: 0...9)
Bool.random() Gen.bool
[1, 2, 3].randomElement() Gen.element(of: [1, 2, 3])
[1, 2, 3].shuffled() Gen.shuffle([1, 2, 3])

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, strings, 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"

This kind of composition makes it simple to generate random values of anything.

// Use `zip` to combine generators together and build structures.

let randomPoint = zip(.int(in: -10...10), .int(in: -10...10))
  .map(CGPoint.init(x:y:))
// Gen<CGPoint>

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 xoshiro = Xoshiro(seed: 0)
Gen.int(in: 0...9).run(using: &xoshiro) // "1"
Gen.int(in: 0...9).run(using: &xoshiro) // "0"
Gen.int(in: 0...9).run(using: &xoshiro) // "4"

xoshiro = Xoshiro(seed: 0)
Gen.int(in: 0...9).run(using: &xoshiro) // "1"
Gen.int(in: 0...9).run(using: &xoshiro) // "0"
Gen.int(in: 0...9).run(using: &xoshiro) // "4"

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

For more examples of using Gen to build complex randomness, see our blog post on creating a Zalgo generator and our two-part video series (part 1 and part 2) on creating generative art.

Installation

If you want to use Gen in a project that uses SwiftPM, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-gen.git", from: "0.4.0")
]

Interested in learning more?

These concepts (and more) are explored thoroughly in Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

The design of this library was explored in the following Point-Free episodes:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

swift-gen's People

Contributors

broomburgo avatar mbrandonw avatar stephencelis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swift-gen's Issues

Build fails on DTK

Hi Brandon, Stephen,

just a little heads up: Gen is currently failing to build on macOS/ARM:

env DEVELOPER_DIR="/Applications/Xcode_12_beta_6.app" xcrun swift build
/Users/builder/Downloads/checkout/Sources/Gen/Gen.swift:279:30: error: 'Float80' is unavailable: Float80 is not available on target platform.
extension Gen where Value == Float80 {
                             ^~~~~~~
Swift.Float80:2:23: note: 'Float80' has been explicitly marked unavailable here
@frozen public struct Float80 {
                      ^
/Users/builder/Downloads/checkout/Sources/Gen/Gen.swift:285:40: error: type 'Value' does not conform to protocol 'Comparable'
  public static func float80(in range: ClosedRange<Value>) -> Gen {
                                       ^
/Users/builder/Downloads/checkout/Sources/Gen/Gen.swift:286:12: error: type of expression is ambiguous without more context
    return Gen { rng in .random(in: range, using: &rng) }
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[5/5] Compiling Gen Zip.swift

(both 12b4 and 12b6). See also: https://swiftpackageindex.com/pointfreeco/swift-gen/builds

Not sure what the best fix here would be - perhaps exclude the Float80 overload on ARM for now?

swift build -c release crashes on Linux

I haven't been able to dig deeper into this yet but it appears that building the package with -c release crashes the compiler:

# swift build -c release
swift: /home/buildnode/jenkins/workspace/oss-swift-5.0-package-linux-ubuntu-18_04/swift/lib/AST/Type.cpp:4079: swift::SILBoxType::SILBoxType(swift::ASTContext &, swift::SILLayout *, swift::SubstitutionMap): Assertion `Substitutions.isCanonical()' failed.
Stack dump:
0.	Program arguments: /usr/bin/swift -frontend -c /host/.build/checkouts/swift-gen/Sources/Gen/AnyRandomNumberGenerator.swift /host/.build/checkouts/swift-gen/Sources/Gen/Gen.swift /host/.build/checkouts/swift-gen/Sources/Gen/LCRNG.swift /host/.build/checkouts/swift-gen/Sources/Gen/UIKit.swift /host/.build/checkouts/swift-gen/Sources/Gen/Zip.swift -supplementary-output-file-map /tmp/supplementaryOutputs-dc6044 -target x86_64-unknown-linux -disable-objc-interop -sdk / -I /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release -module-cache-path /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/ModuleCache -swift-version 5 -O -D SWIFT_PACKAGE -color-diagnostics -parse-as-library -module-name Gen -num-threads 4 -o /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/Gen.build/AnyRandomNumberGenerator.swift.o -o /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/Gen.build/Gen.swift.o -o /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/Gen.build/LCRNG.swift.o -o /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/Gen.build/UIKit.swift.o -o /host/.build/checkouts/swift-gen/.build/x86_64-unknown-linux/release/Gen.build/Zip.swift.o
1.	While running pass #25445 SILFunctionTransform "GenericSpecializer" on SILFunction "@globalinit_33_037C8324581EFD9286D2C4467C9955FB_func4".
 for declaration 0x7f51e30 (at /host/.build/checkouts/swift-gen/Sources/Gen/Gen.swift:508:10)
/usr/bin/swift[0x42420c4]
/usr/bin/swift[0x423fe4e]
/usr/bin/swift[0x4242282]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x12890)[0x7f10e53a3890]
/lib/x86_64-linux-gnu/libc.so.6(gsignal+0xc7)[0x7f10e3806e97]
/lib/x86_64-linux-gnu/libc.so.6(abort+0x141)[0x7f10e3808801]
/lib/x86_64-linux-gnu/libc.so.6(+0x3039a)[0x7f10e37f839a]
/lib/x86_64-linux-gnu/libc.so.6(+0x30412)[0x7f10e37f8412]
/usr/bin/swift[0x18b8b0a]
/usr/bin/swift[0x17634d5]
/usr/bin/swift[0x18b9645]
/usr/bin/swift[0x18b6136]
/usr/bin/swift[0x18b4671]
/usr/bin/swift[0x18b4fa7]
/usr/bin/swift[0x12e4b58]
/usr/bin/swift[0x12debce]
/usr/bin/swift[0x12dec93]
/usr/bin/swift[0x122598d]
/usr/bin/swift[0x1228166]
/usr/bin/swift[0x1228eca]
/usr/bin/swift[0x12278ac]
/usr/bin/swift[0x1225bee]
/usr/bin/swift[0x12256ae]
/usr/bin/swift[0xfe57f9]
/usr/bin/swift[0xfe5588]
/usr/bin/swift[0xfe6d34]
/usr/bin/swift[0xf3f0a9]
/usr/bin/swift[0xe9a372]
/usr/bin/swift[0xe9b0e2]
/usr/bin/swift[0xe9c1ff]
/usr/bin/swift[0x5b5008]
/usr/bin/swift[0xea3fba]
/usr/bin/swift[0x4c19f1]
/usr/bin/swift[0x4bd809]
/usr/bin/swift[0x46e670]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x7f10e37e9b97]
/usr/bin/swift[0x46ca8a]
<unknown>:0: error: unable to execute command: Aborted
<unknown>:0: error: compile command failed due to signal 6 (use -v to see invocation)

# swift build
[1/1] Compiling Swift Module 'Gen' (5 sources)
#

Posting it here but I wonder if that's not something for the swift bug reporter instead? What do you think?

iOS target does not compile

Gen_iOS fails due to Gen.swift:278:30: 'Float80' is unavailable: Float80 is only available on non-Windows x86 targets.

All other targets work fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.