diff --git a/.gitmodules b/.gitmodules index 70cf4a63..efd56c80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,20 +2,23 @@ path = xyzw-templating-languages/Vendor/Stencil url = https://github.com/stencilproject/Stencil.git [submodule "0017-styling-pt2/Vendor/swift-overture"] - path = 0017-styling-pt2/Vendor/swift-overture - url = https://github.com/pointfreeco/swift-overture.git + path = 0017-styling-pt2/Vendor/swift-overture + url = https://github.com/pointfreeco/swift-overture.git [submodule "0018-environment-pt2/Vendor/swift-overture"] - path = 0018-environment-pt2/Vendor/swift-overture - url = https://github.com/pointfreeco/swift-overture.git + path = 0018-environment-pt2/Vendor/swift-overture + url = https://github.com/pointfreeco/swift-overture.git [submodule "0021-playground-driven-development/Vendor/swift-overture"] - path = 0021-playground-driven-development/Vendor/swift-overture - url = https://github.com/pointfreeco/swift-overture.git + path = 0021-playground-driven-development/Vendor/swift-overture + url = https://github.com/pointfreeco/swift-overture.git [submodule "0024-zip-pt2/Vendor/swift-nonempty"] - path = 0024-zip-pt2/Vendor/swift-nonempty - url = https://github.com/pointfreeco/swift-nonempty.git + path = 0024-zip-pt2/Vendor/swift-nonempty + url = https://github.com/pointfreeco/swift-nonempty.git [submodule "0025-zip-pt3/Vendor/swift-nonempty"] - path = 0025-zip-pt3/Vendor/swift-nonempty - url = https://github.com/pointfreeco/swift-nonempty.git -[submodule "0031-arbitrary/Vendor/Tagged"] + path = 0025-zip-pt3/Vendor/swift-nonempty + url = https://github.com/pointfreeco/swift-nonempty.git +[submodule "0031-arbitrary-pt1/Vendor/Tagged"] path = 0031-arbitrary-pt1/Vendor/Tagged url = https://github.com/pointfreeco/swift-tagged.git +[submodule "0032-arbitrary-pt2/Vendor/Tagged"] + path = 0032-arbitrary-pt2/Vendor/Tagged + url = https://github.com/pointfreeco/swift-tagged.git diff --git a/0031-arbitrary-pt1/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0031-arbitrary-pt1/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift index 20fa4686..fd3a7c8f 100644 --- a/0031-arbitrary-pt1/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift +++ b/0031-arbitrary-pt1/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift @@ -1,5 +1,5 @@ /*: - # Decodable Randomness + # Decodable Randomness: Part 1 ## Exercises diff --git a/0031-arbitrary-pt1/Arbitrary.playground/contents.xcplayground b/0031-arbitrary-pt1/Arbitrary.playground/contents.xcplayground index 6dd3774b..d4a13862 100644 --- a/0031-arbitrary-pt1/Arbitrary.playground/contents.xcplayground +++ b/0031-arbitrary-pt1/Arbitrary.playground/contents.xcplayground @@ -1,7 +1,2 @@ - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/0031-arbitrary-pt1/README.md b/0031-arbitrary-pt1/README.md index bc46e3d5..3e1bd109 100644 --- a/0031-arbitrary-pt1/README.md +++ b/0031-arbitrary-pt1/README.md @@ -1,5 +1,5 @@ ## [Point-Free](https://www.pointfree.co) -> #### This directory contains code from Point-Free Episode: [Arbitrary TODO](https://www.pointfree.co/episodes/ep31-arbitrary-TODO) +> #### This directory contains code from Point-Free Episode: [Decodable Randomness: Part 1](https://www.pointfree.co/episodes/ep31-decodable-randomness-part-1) > -> TODO +> This week we dive deeper into randomness and composition by looking to a seemingly random place: the `Decodable` protocol. While we’re used to using the `Codable` set of protocols when working with JSON serialization and deserialization, it opens the opportunity for so much more. diff --git a/0032-arbitrary-pt2/Arbitrary.playground/Pages/01-episode.xcplaygroundpage/Contents.swift b/0032-arbitrary-pt2/Arbitrary.playground/Pages/01-episode.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..103eeca2 --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.playground/Pages/01-episode.xcplaygroundpage/Contents.swift @@ -0,0 +1,335 @@ + +//Decodable + +import Foundation +//JSONDecoder().decode(<#T##type: Decodable.Protocol##Decodable.Protocol#>, from: <#T##Data#>) + +struct ArbitraryDecoder: Decoder { + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey: Any] = [:] + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + + return KeyedDecodingContainer(KeyedContainer()) + } + + struct KeyedContainer: KeyedDecodingContainerProtocol { + var codingPath: [CodingKey] = [] + var allKeys: [Key] = [] + + func contains(_ key: Key) -> Bool { + fatalError() + } + + func decodeNil(forKey key: Key) throws -> Bool { + fatalError() + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + return try T(from: ArbitraryDecoder()) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + fatalError() + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + fatalError() + } + + func superDecoder() throws -> Decoder { + fatalError() + } + + func superDecoder(forKey key: Key) throws -> Decoder { + fatalError() + } + + + + + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + fatalError() + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + return SingleValueContainer() + } + + struct SingleValueContainer: SingleValueDecodingContainer { + var codingPath: [CodingKey] = [] + + func decodeNil() -> Bool { + return .random() + } + + func decode(_ type: Bool.Type) throws -> Bool { + return .random() + } + + func decode(_ type: String.Type) throws -> String { + return Array(repeating: (), count: .random(in: 0...280)) + .map { String(UnicodeScalar(UInt8.random(in: .min ... .max))) } + .joined() + } + + func decode(_ type: Double.Type) throws -> Double { + return .random(in: -1_000_000_000...1_000_000_000) + } + + func decode(_ type: Float.Type) throws -> Float { + return .random(in: 0...1) + } + + func decode(_ type: Int.Type) throws -> Int { + return .random(in: .min ... .max) + } + + func decode(_ type: Int8.Type) throws -> Int8 { + return .random(in: .min ... .max) + } + + func decode(_ type: Int16.Type) throws -> Int16 { + return .random(in: .min ... .max) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + return .random(in: .min ... .max) + } + + func decode(_ type: Int64.Type) throws -> Int64 { + return .random(in: .min ... .max) + } + + func decode(_ type: UInt.Type) throws -> UInt { + return .random(in: .min ... .max) + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + return .random(in: .min ... .max) + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + return .random(in: .min ... .max) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + return .random(in: .min ... .max) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + return .random(in: .min ... .max) + } + + func decode(_ type: T.Type) throws -> T where T: Decodable { + return try T(from: ArbitraryDecoder()) + } + } +} + +try Bool(from: ArbitraryDecoder()) +try Bool(from: ArbitraryDecoder()) +try Bool(from: ArbitraryDecoder()) +try Bool(from: ArbitraryDecoder()) +try Bool(from: ArbitraryDecoder()) +try Int(from: ArbitraryDecoder()) +try Int(from: ArbitraryDecoder()) +try Int(from: ArbitraryDecoder()) +try Int(from: ArbitraryDecoder()) +try Int(from: ArbitraryDecoder()) +try UInt8(from: ArbitraryDecoder()) +try UInt8(from: ArbitraryDecoder()) +try UInt8(from: ArbitraryDecoder()) +try UInt8(from: ArbitraryDecoder()) +try UInt8(from: ArbitraryDecoder()) +try Double(from: ArbitraryDecoder()) +try Double(from: ArbitraryDecoder()) +try Double(from: ArbitraryDecoder()) +try Double(from: ArbitraryDecoder()) +try Double(from: ArbitraryDecoder()) +try Float(from: ArbitraryDecoder()) +try Float(from: ArbitraryDecoder()) +try Float(from: ArbitraryDecoder()) +try Float(from: ArbitraryDecoder()) +try Float(from: ArbitraryDecoder()) +try String(from: ArbitraryDecoder()) +try String(from: ArbitraryDecoder()) +try String(from: ArbitraryDecoder()) +try String(from: ArbitraryDecoder()) +try String(from: ArbitraryDecoder()) +try String?(from: ArbitraryDecoder()) +try String?(from: ArbitraryDecoder()) +try String?(from: ArbitraryDecoder()) +try String?(from: ArbitraryDecoder()) +try String?(from: ArbitraryDecoder()) +try Date(from: ArbitraryDecoder()) +try Date(from: ArbitraryDecoder()) +try Date(from: ArbitraryDecoder()) +try Date(from: ArbitraryDecoder()) +try Date(from: ArbitraryDecoder()) +//try UUID(from: ArbitraryDecoder()) + +import Tagged + +struct User: Decodable { + typealias Id = Tagged + + let id: Id + let name: String + let email: String +} + +//print(try User(from: ArbitraryDecoder())) +//print(try User(from: ArbitraryDecoder())) +//print(try User(from: ArbitraryDecoder())) +//print(try User(from: ArbitraryDecoder())) + + +struct Gen { + let run: () -> A +} + +import Darwin +let random = Gen(run: arc4random) + +extension Gen { + func map(_ f: @escaping (A) -> B) -> Gen { + return Gen { f(self.run()) } + } +} + +let uint64 = Gen { + let lower = UInt64(random.run()) + let upper = UInt64(random.run()) << 32 + return lower + upper +} + +func int(in range: ClosedRange) -> Gen { + return Gen { + var delta = UInt64(truncatingIfNeeded: range.upperBound &- range.lowerBound) + if delta == UInt64.max { + return Int(truncatingIfNeeded: uint64.run()) + } + delta += 1 + let tmp = UInt64.max % delta + 1 + let upperBound = tmp == delta ? 0 : tmp + var random: UInt64 = 0 + repeat { + random = uint64.run() + } while random < upperBound + return Int( + truncatingIfNeeded: UInt64(truncatingIfNeeded: range.lowerBound) + &+ random % delta + ) + } +} + +func element(of xs: [A]) -> Gen { + return int(in: 0...(xs.count - 1)).map { idx in + guard !xs.isEmpty else { return nil } + return xs[idx] + } +} + +extension Gen { + func array(count: Gen) -> Gen<[A]> { + return Gen<[A]> { + Array(repeating: (), count: count.run()) + .map { self.run() } + } + } +} + +func uint8(in range: ClosedRange) -> Gen { + return int(in: Int(UInt8.min)...Int(UInt8.max)) + .map(UInt8.init) +} + +let string = uint8(in: .min ... .max) +// .map { String(UnicodeScalar($0)) } +// .map(UnicodeScalar.init) +// .map(String.init) + .map(UnicodeScalar.init >>> String.init) + .array(count: int(in: 0...280)) + .map { $0.joined() } + +string.run() +string.run() +string.run() + +"DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF" + + +extension Gen where A == Character { + func string(count: Gen) -> Gen { + return self.map(String.init).array(count: count).map { $0.joined() } + } +} + +let hex = element(of: Array("0123456789ABCDEF")).map { $0! } +let uuidString = Gen { + hex.string(count: .init { 8 }).run() + + "-" + hex.string(count: .init { 4 }).run() + + "-" + hex.string(count: .init { 4 }).run() + + "-" + hex.string(count: .init { 4 }).run() + + "-" + hex.string(count: .init { 12 }).run() +} +let randomUuid = uuidString.map(UUID.init).map { $0! } + +User( + id: randomUuid.map(User.Id.init).run(), + name: string.run(), + email: string.run() +) + + +let alpha = element(of: Array("abcdefghijklmnopqrstuvwxyz")).map { $0! } +let namePart = alpha.string(count: int(in: 4...8)) +let capitalNamePart = namePart.map { $0.capitalized } +let randomName = Gen { capitalNamePart.run() + " " + capitalNamePart.run() } +let randomEmail = namePart.map { $0 + "@pointfree.co" } +let randomId = int(in: 1...1_000) + + +func zip2(_ a: Gen, _ b: Gen) -> Gen<(A, B)> { + return Gen<(A, B)> { + (a.run(), b.run()) + } +} + +zip2(randomId, randomName).run() +zip2(randomId, randomName).run() +zip2(randomId, randomName).run() + +func zip2(with f: @escaping (A, B) -> C) -> (Gen, Gen) -> Gen { + return { zip2($0, $1).map(f)} +} + +func zip3(_ a: Gen, _ b: Gen, _ c: Gen) -> Gen<(A, B, C)> { + return zip2(a, zip2(b, c)).map { ($0, $1.0, $1.1) } +} + +func zip3(with f: @escaping (A, B, C) -> D) -> (Gen, Gen, Gen) -> Gen { + return { zip3($0, $1, $2).map(f) } +} + +let randomUser = zip3(with: User.init)( + randomUuid.map(User.Id.init), + randomName, + randomEmail +) + +//let randomUser = Gen { +// User( +// id: randomId.run(), +// name: randomName.run(), +// email: randomEmail.run() +// ) +//} + +print(randomUser.run()) +print(randomUser.run()) +print(randomUser.run()) +print(randomUser.run()) diff --git a/0032-arbitrary-pt2/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0032-arbitrary-pt2/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..906dd867 --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift @@ -0,0 +1,30 @@ +/*: + # Decodable Randomness: Part 2 + + ## Exercises + + 1.) Redefine `Gen`'s base unit of randomness, `random`, which is a `Gen` to work with Swift 4.2's base unit of randomness, the `RandomNumberGenerator` protocol. The base random type should should change to `UInt64`. + */ +// TODO +/*: + 2.) Swift 4.2's protocol-oriented solution allows us to define custom types that conform to `RandomNumberGenerator`. Update `Gen` to evaluate given any `RandomNumberGenerator` by changing `run`'s signature. + */ +// TODO +/*: + 3.) Use a custom random number generator that can be configured with a stable seed to allow for the `Gen` type to predictably generate the same random value for a given seed. + + You can look to [Nate Cook's playground](https://forums.swift.org/t/se-0202-random-unification/11313/30), shared on the Swift forums, or (for bonus points), you can define your own [linear congruential generator](https://en.wikipedia.org/wiki/Linear_congruential_generator) (or LCG). + */ +// TODO +/*: + 4.) Write a helper that runs a property test for `XCTest`! A property test, given a generator and a block of code, will evaluate the block of code with a configurable number of random runs. If the block returns `true`, the property test passes. It it returns `false`, it fails. The signature should be the following. + + func forAll(_ a: Gen, propertyShouldHold: (A) -> Bool) + + It should, internally, call an `XCTAssert` function. Upon failure, print out the seed so that it can be reproduced. + */ +// TODO +/*: + 5.) Enhance the `forAll` API to take the parameters `file: StaticString = #file` and `line: UInt = #line`, which can be passed to XCTest assertions in order to highlight the correct line on failure. + */ +// TODO diff --git a/0032-arbitrary-pt2/Arbitrary.playground/Sources/Utils.swift b/0032-arbitrary-pt2/Arbitrary.playground/Sources/Utils.swift new file mode 100644 index 00000000..9485da49 --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.playground/Sources/Utils.swift @@ -0,0 +1,7 @@ +precedencegroup ForwardCompose { + associativity: left +} +infix operator >>>: ForwardCompose +public func >>> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C { + return { g(f($0)) } +} diff --git a/0032-arbitrary-pt2/Arbitrary.playground/contents.xcplayground b/0032-arbitrary-pt2/Arbitrary.playground/contents.xcplayground new file mode 100644 index 00000000..f6677ad0 --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.playground/contents.xcplayground @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/0032-arbitrary-pt2/Arbitrary.xcworkspace/contents.xcworkspacedata b/0032-arbitrary-pt2/Arbitrary.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..b85f969b --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/0032-arbitrary-pt2/Arbitrary.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0032-arbitrary-pt2/Arbitrary.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0032-arbitrary-pt2/Arbitrary.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0032-arbitrary-pt2/README.md b/0032-arbitrary-pt2/README.md new file mode 100644 index 00000000..57a9433b --- /dev/null +++ b/0032-arbitrary-pt2/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Decodable Randomness: Part 2](https://www.pointfree.co/episodes/ep32-decodable-randomness-part-2) +> +> This week we compare our `Decodable` solution to building random structures with a composable solution involving the `Gen` type, exploring the differences and trade-offs of each approach. Along the way we'll rediscover a familiar old friend with a brand new application.