diff --git a/0044-the-many-faces-of-flatmap-pt3/README.md b/0044-the-many-faces-of-flatmap-pt3/README.md index bb448a81..4b78e648 100644 --- a/0044-the-many-faces-of-flatmap-pt3/README.md +++ b/0044-the-many-faces-of-flatmap-pt3/README.md @@ -1,6 +1,6 @@ ## [Point-Free](https://www.pointfree.co) -> #### This directory contains code from Point-Free Episode: [The Many Faces of Flat-Map: Part 2](https://www.pointfree.co/episodes/ep43-the-many-faces-of-flat-map-part-2) +> #### This directory contains code from Point-Free Episode: [The Many Faces of Flat-Map: Part 3](https://www.pointfree.co/episodes/ep44-the-many-faces-of-flat-map-part-3) > > We are now ready to answer the all-important question: what's the point? We will describe 3 important ideas that are now more accessible due to our deep study of `map`, `zip` and `flatMap`. We will start by showing that this trio of operations forms a kind of functional, domain-specific language for data transformations. @@ -9,6 +9,6 @@ * Clone repo * `cd` into this directory * Run `swift package generate-xcodeproj` -* Open `ManyFacesOfFlatMapPt.xcworkspace` +* Open `ManyFacesOfFlatMap.xcworkspace` * Build the package for _macOS_ * Open the playground diff --git a/0045-the-many-faces-of-flatmap-pt4/.gitignore b/0045-the-many-faces-of-flatmap-pt4/.gitignore new file mode 100644 index 00000000..02c08753 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Contents.swift b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Contents.swift new file mode 100644 index 00000000..59d12fbe --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Contents.swift @@ -0,0 +1,470 @@ +import Foundation + +func combos(_ xs: [A], _ ys: [B]) -> [(A, B)] { + return xs.flatMap { x in + ys.map { y in + (x, y) + } + } +} + +enum Result { + case success(A) + case failure(E) + + func map(_ f: @escaping (A) -> B) -> Result { + switch self { + case let .success(a): + return .success(f(a)) + case let .failure(e): + return .failure(e) + } + } + + public func flatMap(_ transform: (A) -> Result) -> Result { + switch self { + case let .success(value): + return transform(value) + case let .failure(error): + return .failure(error) + } + } +} + +import NonEmpty + +enum Validated { + case valid(A) + case invalid(NonEmptyArray) + + func map(_ f: @escaping (A) -> B) -> Validated { + switch self { + case let .valid(a): + return .valid(f(a)) + case let .invalid(e): + return .invalid(e) + } + } + + + public func flatMap(_ transform: (A) -> Validated) -> Validated { + switch self { + case let .valid(value): + return transform(value) + case let .invalid(error): + return .invalid(error) + } + } +} + +struct Func { + let run: (A) -> B + + func map(_ f: @escaping (B) -> C) -> Func { + return Func(run: self.run >>> f) + } + + func flatMap(_ f: @escaping (B) -> Func) -> Func { + return Func { a -> C in + f(self.run(a)).run(a) + } + } +} + +let randomNumber = Func { + let number = try! String(contentsOf: URL(string: "https://www.random.org/integers/?num=1&min=1&max=235866&col=1&base=10&format=plain&rnd=new")!) + .trimmingCharacters(in: .newlines) + return Int(number)! +} + +let words = Func { + (try! String(contentsOf: URL(fileURLWithPath: "/usr/share/dict/words"))) + .split(separator: "\n") + .map(String.init) +} + +struct Parallel { + let run: (@escaping (A) -> Void) -> Void + + func map(_ f: @escaping (A) -> B) -> Parallel { + return Parallel { callback in + self.run { a in callback(f(a)) } + } + } + + func flatMap(_ f: @escaping (A) -> Parallel) -> Parallel { + return Parallel { callback in + self.run { a in + f(a).run(callback) + } + } + } +} + +func delay(by duration: TimeInterval, line: UInt = #line) -> Parallel { + return Parallel { callback in + print("Delaying line \(line) by \(duration)") + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + print("Finished line \(line)") + callback(()) + } + } +} + + + +struct User: Codable { + let email: String + let id: Int + let name: String +} + +//let user: User? +//if let path = Bundle.main.path(forResource: "user", ofType: "json"), +// case let url = URL.init(fileURLWithPath: path), +// let data = try? Data.init(contentsOf: url) { +// +// user = try? JSONDecoder().decode(User.self, from: data) +//} else { +// user = nil +//} + +let newUser = Bundle.main.path(forResource: "user", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode(User.self, from: $0) } + + +struct Invoice: Codable { + let amountDue: Int + let amountPaid: Int + let closed: Bool + let id: Int +} + +let invoices = Bundle.main.path(forResource: "invoices", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } + +if let newUser = newUser, let invoices = invoices { + // +} + +func zip(_ a: A?, _ b: B?) -> (A, B)? { + if let a = a, let b = b { return (a, b) } + return nil +} + +zip(newUser, invoices) + +struct UserEnvelope { + let user: User + let invoices: [Invoice] +} + +func zip(with f: @escaping (A, B) -> C) -> (A?, B?) -> C? { + return { zip($0, $1).map(f) } +} + +struct SomeExpected: Error {} + +func requireSome(_ a: A?) throws -> A { + guard let a = a else { throw SomeExpected() } + return a +} + +do { + let path = try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) + let url = URL.init(fileURLWithPath: path) + let data = try Data.init(contentsOf: url) + let user = try JSONDecoder().decode(User.self, from: data) +} catch { + +} + + +extension Result where E == Swift.Error { + init(catching f: () throws -> A) { + do { + self = .success(try f()) + } catch { + self = .failure(error) + } + } +} + +extension Validated where E == Swift.Error { + init(catching f: () throws -> A) { + do { + self = .valid(try f()) + } catch { + self = .invalid(NonEmptyArray(error)) + } + } +} + + + +func zip(_ a: Result, _ b: Result) -> Result<(A, B), E> { + switch (a, b) { + case let (.success(a), .success(b)): + return .success((a, b)) + case let (.failure(e), _): + return .failure(e) + case let (.success, .failure(e)): + return .failure(e) + } +} + + +func zip(with f: @escaping (A, B) -> C) -> (Result, Result) -> Result { + return { zip($0, $1).map(f) } +} + + +func zip(_ a: Validated, _ b: Validated) -> Validated<(A, B), E> { + switch (a, b) { + case let (.valid(a), .valid(b)): + return .valid((a, b)) + case let (.valid, .invalid(e)): + return .invalid(e) + case let (.invalid(e), .valid): + return .invalid(e) + case let (.invalid(e1), .invalid(e2)): + return .invalid(e1 + e2) + } +} + +func zip(with f: @escaping (A, B) -> C) -> (Validated, Validated) -> Validated { + return { zip($0, $1).map(f) } +} + + +zip(with: UserEnvelope.init)( + Bundle.main.path(forResource: "user", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode(User.self, from: $0) }, + + Bundle.main.path(forResource: "invoices", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } +) + +// failure(Swift.DecodingError.typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))) +zip(with: UserEnvelope.init)( + Result { try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Result { try Data.init(contentsOf: url) } } + .flatMap { data in Result { try JSONDecoder().decode(User.self, from: data) } }, + + Result { try requireSome(Bundle.main.path(forResource: "invoices", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Result { try Data.init(contentsOf: url) } } + .flatMap { data in Result { try JSONDecoder().decode([Invoice].self, from: data) } } +) + +// invalid(typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))[Swift.DecodingError.typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "amountDue", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))]) +zip(with: UserEnvelope.init)( + Validated { try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Validated { try Data.init(contentsOf: url) } } + .flatMap { data in Validated { try JSONDecoder().decode(User.self, from: data) } }, + + Validated { try requireSome(Bundle.main.path(forResource: "invoices", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Validated { try Data.init(contentsOf: url) } } + .flatMap { data in Validated { try JSONDecoder().decode([Invoice].self, from: data) } } +) + +let lazyUser = Func { Bundle.main.path(forResource: "user", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode(User.self, from: data) } } + +lazyUser.run(()) + + +func zip(_ ab: Func, _ ac: Func) -> Func { + return Func { a in + (ab.run(a), ac.run(a)) + } +} + +func zip(with f: @escaping (B, C) -> D) -> (Func, Func) -> Func { + return { zip($0, $1).map(f) } +} + +zip(with: UserEnvelope.init)( + Func { Bundle.main.path(forResource: "user", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode(User.self, from: data) } }, + + Func { Bundle.main.path(forResource: "invoices", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode([Invoice].self, from: data) } } +) + +extension Parallel { + init(_ work: @autoclosure @escaping () -> A) { + self = Parallel { callback in + DispatchQueue.global().async { + callback(work()) + } + } + } +} + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } + +func zip(_ pa: Parallel, _ pb: Parallel) -> Parallel<(A, B)> { + return Parallel<(A, B)> { callback in + var optionalA: A? + var optionalB: B? + pa.run { a in + optionalA = a + if let b = optionalB { callback((a, b)) } + } + pb.run { b in + optionalB = b + if let a = optionalA { callback((a, b)) } + } + } +} + +func zip(with f: @escaping (A, B) -> C) -> (Parallel, Parallel) -> Parallel { + return { zip($0, $1).map(f) } +} + +zip(with: UserEnvelope.init)( + Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) }, + + Parallel(Bundle.main.path(forResource: "invoices", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode([Invoice].self, from: data)) } + ).run { env in +// print(env) +} + +// +//do { +// let user = try JSONDecoder().decode( +// User.self, +// from: Data.init( +// contentsOf: URL.init( +// fileURLWithPath: requireSome( +// Bundle.main.path( +// forResource: "user", +// ofType: "json" +// ) +// ) +// ) +// ) +// ) +//} catch { +// +//} + + +//extension Sequence { +// public func flatMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] +//} + +// flatMap: ((A) -> B?) -> ((F) -> F) + + +//func fromThrowing(_ f: @escaping (A) throws -> B) -> (A) -> Result { +// return { a in +// do { +// return .success(try f(a)) +// } catch let error { +// return .failure(error) +// } +// } +//} +// +//func toThrowing(_ f: @escaping (A) -> Result) -> ((A) throws -> B) { +// return { a in +// switch f(a) { +// case let .success(value): +// return value +// case let .failure(error): +// throw error +// } +// } +//} +// +//extension Result { +// func map(_ f: @escaping (A) -> Result) -> Result { +// fatalError() +// } +// +// func flatMap(_ f: @escaping (A) -> Result, Swift.Error>) -> Result { +// fatalError() +// } +//} + +//extension Func /* */ { +// func flatMap(_ f: @escaping (A) -> Func) -> Func { +// +// } +//} + +import XCTest +struct Diffing { + let toData: (Value) -> Data + let fromData: (Data) -> Value + let diff: (Value, Value) -> (String, [XCTAttachment])? + + func flatMap(_ f: @escaping (Value) -> Diffing) -> Diffing { + fatalError("Not possible to implement.") + } +} + +struct Snapshotting { + var pathExtension: String? + let diffing: Diffing + let snapshot: (Value) -> Format + + func flatMap(_ f: @escaping (Value) -> Snapshotting) -> Snapshotting { + fatalError("Not possible to implement.") + } + + func flatMap(_ f: @escaping (Format) -> Snapshotting) -> Snapshotting { + fatalError("Not possible to implement.") + } +} + + +extension Parallel { + func then(_ f: @escaping (A) -> Parallel) -> Parallel { + return self.flatMap(f) + } +} + +extension Parallel { + func then(_ f: @escaping (A) -> B) -> Parallel { + return self.map(f) + } +} + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .then(URL.init(fileURLWithPath:)) + .then { url in Parallel(try! Data.init(contentsOf: url)) } + .then { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/invoices.json b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/invoices.json new file mode 100644 index 00000000..8c0af97b --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/invoices.json @@ -0,0 +1,14 @@ +[ + { + "amountPaid": 1000, + "amountDue": 0, + "closed": true, + "id": 1 + }, + { + "amountPaid": 500, + "amountDue": 500, + "closed": false, + "id": 2 + } +] diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/user.json b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/user.json new file mode 100644 index 00000000..ae6fc81d --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Resources/user.json @@ -0,0 +1,5 @@ +{ + "email": "blob@pointfree.co", + "id": 42, + "name": "Blob" +} diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Sources/Util.swift b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Sources/Util.swift new file mode 100644 index 00000000..e8ec0129 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/Sources/Util.swift @@ -0,0 +1,17 @@ +precedencegroup EffectfulComposition { + associativity: left + higherThan: AssignmentPrecedence +} + +infix operator >=>: EffectfulComposition + +precedencegroup ForwardComposition { + associativity: left + higherThan: EffectfulComposition +} + +infix operator >>>: ForwardComposition + +public func >>> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C { + return { a in g(f(a)) } +} diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/contents.xcplayground b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/contents.xcplayground new file mode 100644 index 00000000..a93d4844 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..cb4198ae --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..a72dc2b4 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + \ No newline at end of file diff --git a/0045-the-many-faces-of-flatmap-pt4/Package.resolved b/0045-the-many-faces-of-flatmap-pt4/Package.resolved new file mode 100644 index 00000000..933b69f4 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "NonEmpty", + "repositoryURL": "https://github.com/pointfreeco/swift-nonempty.git", + "state": { + "branch": null, + "revision": "bcf639e8ec6c20b6b7b28ed9bfa0fe83450c3248", + "version": "0.1.2" + } + } + ] + }, + "version": 1 +} diff --git a/0045-the-many-faces-of-flatmap-pt4/Package.swift b/0045-the-many-faces-of-flatmap-pt4/Package.swift new file mode 100644 index 00000000..156e9c16 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:4.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ManyFacesOfFlatMap", + products: [ + // Products define the executables and libraries produced by a package, and make them visible to other packages. + .library( + name: "ManyFacesOfFlatMap", + targets: ["ManyFacesOfFlatMap"]), + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-nonempty.git", from: "0.1.2") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages which this package depends on. + .target( + name: "ManyFacesOfFlatMap", + dependencies: ["NonEmpty"]) + ] +) diff --git a/0045-the-many-faces-of-flatmap-pt4/README.md b/0045-the-many-faces-of-flatmap-pt4/README.md new file mode 100644 index 00000000..aae45fa7 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/README.md @@ -0,0 +1,14 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [The Many Faces of Flat-Map: Part 4](https://www.pointfree.co/episodes/ep45-the-many-faces-of-flat-map-part-4) +> +> Continuing our 3-part answer to the all-important question "what's the point?", we show that the definitions of `map`, `zip` and `flatMap` are precise and concisely describe their purpose. Knowing this we can strengthen our APIs by not smudging their definitions when convenient. + +### Getting Started + +* Clone repo +* `cd` into this directory +* Run `swift package generate-xcodeproj` +* Open `ManyFacesOfFlatMap.xcworkspace` +* Build the package for _macOS_ +* Open the playground diff --git a/0045-the-many-faces-of-flatmap-pt4/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift b/0045-the-many-faces-of-flatmap-pt4/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift new file mode 100644 index 00000000..8fcf9749 --- /dev/null +++ b/0045-the-many-faces-of-flatmap-pt4/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift @@ -0,0 +1,2 @@ + +let x = 1 diff --git a/0046-the-many-faces-of-flatmap-pt5/.gitignore b/0046-the-many-faces-of-flatmap-pt5/.gitignore new file mode 100644 index 00000000..02c08753 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Contents.swift b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Contents.swift new file mode 100644 index 00000000..b63079f2 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Contents.swift @@ -0,0 +1,682 @@ +import Foundation + +func combos(_ xs: [A], _ ys: [B]) -> [(A, B)] { + return xs.flatMap { x in + ys.map { y in + (x, y) + } + } +} + +enum Result { + case success(A) + case failure(E) + + func map(_ f: @escaping (A) -> B) -> Result { + switch self { + case let .success(a): + return .success(f(a)) + case let .failure(e): + return .failure(e) + } + } + + public func flatMap(_ transform: (A) -> Result) -> Result { + switch self { + case let .success(value): + return transform(value) + case let .failure(error): + return .failure(error) + } + } +} + +import NonEmpty + +enum Validated { + case valid(A) + case invalid(NonEmptyArray) + + func map(_ f: @escaping (A) -> B) -> Validated { + switch self { + case let .valid(a): + return .valid(f(a)) + case let .invalid(e): + return .invalid(e) + } + } + + + public func flatMap(_ transform: (A) -> Validated) -> Validated { + switch self { + case let .valid(value): + return transform(value) + case let .invalid(error): + return .invalid(error) + } + } +} + +struct Func { + let run: (A) -> B + + func map(_ f: @escaping (B) -> C) -> Func { + return Func(run: self.run >>> f) + } + + func flatMap(_ f: @escaping (B) -> Func) -> Func { + return Func { a -> C in + f(self.run(a)).run(a) + } + } +} + +let randomNumber = Func { + let number = try! String(contentsOf: URL(string: "https://www.random.org/integers/?num=1&min=1&max=235866&col=1&base=10&format=plain&rnd=new")!) + .trimmingCharacters(in: .newlines) + return Int(number)! +} + +let words = Func { + (try! String(contentsOf: URL(fileURLWithPath: "/usr/share/dict/words"))) + .split(separator: "\n") + .map(String.init) +} + +struct Parallel { + let run: (@escaping (A) -> Void) -> Void + + func map(_ f: @escaping (A) -> B) -> Parallel { + return Parallel { callback in + self.run { a in callback(f(a)) } + } + } + + func flatMap(_ f: @escaping (A) -> Parallel) -> Parallel { + return Parallel { callback in + self.run { a in + f(a).run(callback) + } + } + } +} + +func delay(by duration: TimeInterval, line: UInt = #line) -> Parallel { + return Parallel { callback in + print("Delaying line \(line) by \(duration)") + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + print("Finished line \(line)") + callback(()) + } + } +} + + + +struct User: Codable { + let email: String + let id: Int + let name: String +} + +//let user: User? +//if let path = Bundle.main.path(forResource: "user", ofType: "json"), +// case let url = URL.init(fileURLWithPath: path), +// let data = try? Data.init(contentsOf: url) { +// +// user = try? JSONDecoder().decode(User.self, from: data) +//} else { +// user = nil +//} + +let newUser = Bundle.main.path(forResource: "user", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode(User.self, from: $0) } + + +struct Invoice: Codable { + let amountDue: Int + let amountPaid: Int + let closed: Bool + let id: Int +} + +let invoices = Bundle.main.path(forResource: "invoices", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } + +if let newUser = newUser, let invoices = invoices { + // +} + +func zip(_ a: A?, _ b: B?) -> (A, B)? { + if let a = a, let b = b { return (a, b) } + return nil +} + +zip(newUser, invoices) + +struct UserEnvelope { + let user: User + let invoices: [Invoice] +} + +func zip(with f: @escaping (A, B) -> C) -> (A?, B?) -> C? { + return { zip($0, $1).map(f) } +} + +struct SomeExpected: Error {} + +func requireSome(_ a: A?) throws -> A { + guard let a = a else { throw SomeExpected() } + return a +} + +do { + let path = try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) + let url = URL.init(fileURLWithPath: path) + let data = try Data.init(contentsOf: url) + let user = try JSONDecoder().decode(User.self, from: data) +} catch { + +} + + +extension Result where E == Swift.Error { + init(catching f: () throws -> A) { + do { + self = .success(try f()) + } catch { + self = .failure(error) + } + } +} + +extension Validated where E == Swift.Error { + init(catching f: () throws -> A) { + do { + self = .valid(try f()) + } catch { + self = .invalid(NonEmptyArray(error)) + } + } +} + + + +func zip(_ a: Result, _ b: Result) -> Result<(A, B), E> { + switch (a, b) { + case let (.success(a), .success(b)): + return .success((a, b)) + case let (.failure(e), _): + return .failure(e) + case let (.success, .failure(e)): + return .failure(e) + } +} + + +func zip(with f: @escaping (A, B) -> C) -> (Result, Result) -> Result { + return { zip($0, $1).map(f) } +} + + +func zip(_ a: Validated, _ b: Validated) -> Validated<(A, B), E> { + switch (a, b) { + case let (.valid(a), .valid(b)): + return .valid((a, b)) + case let (.valid, .invalid(e)): + return .invalid(e) + case let (.invalid(e), .valid): + return .invalid(e) + case let (.invalid(e1), .invalid(e2)): + return .invalid(e1 + e2) + } +} + +func zip(with f: @escaping (A, B) -> C) -> (Validated, Validated) -> Validated { + return { zip($0, $1).map(f) } +} + + +zip(with: UserEnvelope.init)( + Bundle.main.path(forResource: "user", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode(User.self, from: $0) }, + + Bundle.main.path(forResource: "invoices", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } +) + +// failure(Swift.DecodingError.typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))) +zip(with: UserEnvelope.init)( + Result { try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Result { try Data.init(contentsOf: url) } } + .flatMap { data in Result { try JSONDecoder().decode(User.self, from: data) } }, + + Result { try requireSome(Bundle.main.path(forResource: "invoices", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Result { try Data.init(contentsOf: url) } } + .flatMap { data in Result { try JSONDecoder().decode([Invoice].self, from: data) } } +) + +// invalid(typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))[Swift.DecodingError.typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "amountDue", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))]) +zip(with: UserEnvelope.init)( + Validated { try requireSome(Bundle.main.path(forResource: "user", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Validated { try Data.init(contentsOf: url) } } + .flatMap { data in Validated { try JSONDecoder().decode(User.self, from: data) } }, + + Validated { try requireSome(Bundle.main.path(forResource: "invoices", ofType: "json")) } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Validated { try Data.init(contentsOf: url) } } + .flatMap { data in Validated { try JSONDecoder().decode([Invoice].self, from: data) } } +) + +let lazyUser = Func { Bundle.main.path(forResource: "user", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode(User.self, from: data) } } + +lazyUser.run(()) + + +func zip(_ ab: Func, _ ac: Func) -> Func { + return Func { a in + (ab.run(a), ac.run(a)) + } +} + +func zip(with f: @escaping (B, C) -> D) -> (Func, Func) -> Func { + return { zip($0, $1).map(f) } +} + +zip(with: UserEnvelope.init)( + Func { Bundle.main.path(forResource: "user", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode(User.self, from: data) } }, + + Func { Bundle.main.path(forResource: "invoices", ofType: "json")! } + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Func { try! Data.init(contentsOf: url) } } + .flatMap { data in Func { try! JSONDecoder().decode([Invoice].self, from: data) } } +) + +extension Parallel { + init(_ work: @autoclosure @escaping () -> A) { + self = Parallel { callback in + DispatchQueue.global().async { + callback(work()) + } + } + } +} + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } + +func zip(_ pa: Parallel, _ pb: Parallel) -> Parallel<(A, B)> { + return Parallel<(A, B)> { callback in + var optionalA: A? + var optionalB: B? + pa.run { a in + optionalA = a + if let b = optionalB { callback((a, b)) } + } + pb.run { b in + optionalB = b + if let a = optionalA { callback((a, b)) } + } + } +} + +func zip(with f: @escaping (A, B) -> C) -> (Parallel, Parallel) -> Parallel { + return { zip($0, $1).map(f) } +} + +zip(with: UserEnvelope.init)( + Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) }, + + Parallel(Bundle.main.path(forResource: "invoices", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode([Invoice].self, from: data)) } + ).run { env in +// print(env) +} + +// +//do { +// let user = try JSONDecoder().decode( +// User.self, +// from: Data.init( +// contentsOf: URL.init( +// fileURLWithPath: requireSome( +// Bundle.main.path( +// forResource: "user", +// ofType: "json" +// ) +// ) +// ) +// ) +// ) +//} catch { +// +//} + + +//extension Sequence { +// public func flatMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] +//} + +// flatMap: ((A) -> B?) -> ((F) -> F) + + +//func fromThrowing(_ f: @escaping (A) throws -> B) -> (A) -> Result { +// return { a in +// do { +// return .success(try f(a)) +// } catch let error { +// return .failure(error) +// } +// } +//} +// +//func toThrowing(_ f: @escaping (A) -> Result) -> ((A) throws -> B) { +// return { a in +// switch f(a) { +// case let .success(value): +// return value +// case let .failure(error): +// throw error +// } +// } +//} +// +//extension Result { +// func map(_ f: @escaping (A) -> Result) -> Result { +// fatalError() +// } +// +// func flatMap(_ f: @escaping (A) -> Result, Swift.Error>) -> Result { +// fatalError() +// } +//} + +//extension Func /* */ { +// func flatMap(_ f: @escaping (A) -> Func) -> Func { +// +// } +//} + +import XCTest +struct Diffing { + let toData: (Value) -> Data + let fromData: (Data) -> Value + let diff: (Value, Value) -> (String, [XCTAttachment])? + + func flatMap(_ f: @escaping (Value) -> Diffing) -> Diffing { + fatalError("Not possible to implement.") + } +} + +struct Snapshotting { + var pathExtension: String? + let diffing: Diffing + let snapshot: (Value) -> Format + + func flatMap(_ f: @escaping (Value) -> Snapshotting) -> Snapshotting { + fatalError("Not possible to implement.") + } + + func flatMap(_ f: @escaping (Format) -> Snapshotting) -> Snapshotting { + fatalError("Not possible to implement.") + } +} + + +extension Parallel { + func then(_ f: @escaping (A) -> Parallel) -> Parallel { + return self.flatMap(f) + } +} + +extension Parallel { + func then(_ f: @escaping (A) -> B) -> Parallel { + return self.map(f) + } +} + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .then(URL.init(fileURLWithPath:)) + .then { url in Parallel(try! Data.init(contentsOf: url)) } + .then { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } + +Parallel(Bundle.main.path(forResource: "user", ofType: "json")!) + .map(URL.init(fileURLWithPath:)) + .flatMap { url in Parallel(try! Data.init(contentsOf: url)) } + .flatMap { data in Parallel(try! JSONDecoder().decode(User.self, from: data)) } + + + + +func pipe( + _ lhs: @escaping (A) -> B, + _ rhs: @escaping (B) -> C + ) -> (A) -> C { + return lhs >>> rhs +} + +pipe({ $0 + 1 }, { $0 * $0 }) +pipe({ $0 + 1 }, String.init) + +let f = { $0 + 1 } + >>> { $0 * $0 } + >>> { $0 + 1 } + + +//_ = { try? Data.init(contentsOf: $0) } +// >>> { try? JSONDecoder().decode(User.self, from: $0) } + +func chain( + _ lhs: @escaping (A) -> B?, + _ rhs: @escaping (B) -> C? + ) -> (A) -> C? { + return { a in + lhs(a).flatMap(rhs) + } +} + +func >=> ( + _ lhs: @escaping (A) -> B?, + _ rhs: @escaping (B) -> C? + ) -> (A) -> C? { + return { a in + lhs(a).flatMap(rhs) + } +} + + +pipe( + URL.init(fileURLWithPath:), + chain( + { try? Data.init(contentsOf: $0) }, + { try? JSONDecoder().decode(User.self, from: $0) } + ) +) + +let loadUser = URL.init(fileURLWithPath:) + >>> { try? Data.init(contentsOf: $0) } + >=> { try? JSONDecoder().decode(User.self, from: $0) } + +Bundle.main.path(forResource: "user", ofType: "json") + .map(URL.init(fileURLWithPath:)) + .flatMap { try? Data.init(contentsOf: $0) } + .flatMap { try? JSONDecoder().decode(User.self, from: $0) } + +Bundle.main.path(forResource: "user", ofType: "json") + .flatMap(loadUser) + + +// Parallel> + +func map( + _ f: @escaping (A) -> B + ) -> (Parallel>) -> Parallel> { + + return { parallelResultA in + parallelResultA.map { resultA in + resultA.map { a in + f(a) + } + } + } +} + +func zip( + _ lhs: Parallel>, + _ rhs: Parallel> + ) -> Parallel> { + + return zip(with: zip)(lhs, rhs) + +// return zip(lhs, rhs).map { resultA, resultB in +// zip(resultA, resultB) +// } +} + +func flatMap( + _ f: @escaping (A) -> Parallel> + ) -> (Parallel>) -> Parallel> { + + return { parallelResultA in + parallelResultA.flatMap { resultA in + Parallel> { callback in + switch resultA { + case let .success(a): + f(a).run { resultB in callback(resultB) } + case let .failure(error): + callback(.failure(error)) + } + } + } + } +} + + +extension Optional { + func newMap(_ f: (Wrapped) -> NewWrapped) -> NewWrapped? { + return self.flatMap { Optional.some(f($0)) } + } +} + +extension Array { + func newMap(_ f: (Element) -> NewElement) -> [NewElement] { + return self.flatMap { [f($0)] } + } +} + +extension Result { + func newMap(_ f: (A) -> B) -> Result { + return self.flatMap { .success(f($0)) } + } +} + +extension Validated { + func newMap(_ f: (A) -> B) -> Validated { + return self.flatMap { .valid(f($0)) } + } +} + +extension Func { + func newMap(_ f: @escaping (B) -> C) -> Func { + return self.flatMap { b in Func { _ in f(b) } } + } +} + +extension Parallel { + func newMap(_ f: @escaping (A) -> B) -> Parallel { + return self.flatMap { a in Parallel { callback in callback(f(a)) } } + } +} + +func newZip(_ a: A?, _ b: B?) -> (A, B)? { + return a.flatMap { a in + b.flatMap { b in + Optional.some((a, b)) + } + } +} + +func newZip(_ a: [A], _ b: [B]) -> [(A, B)] { + return a.flatMap { a in + b.flatMap { b in + [(a, b)] + } + } +} + +newZip(["a", "b"], [1, 2]) + +func newZip(_ a: Result, _ b: Result) -> Result<(A, B), E> { + return a.flatMap { a in + b.flatMap { b in + Result.success((a, b)) + } + } +} + +func newZip(_ a: Validated, _ b: Validated) -> Validated<(A, B), E> { + return a.flatMap { a in + b.flatMap { b in + Validated.valid((a, b)) + } + } +} + +newZip(Validated.valid(1), .valid("Two")) + + +newZip(Validated.invalid(NonEmptyArray("Something went wrong.")), .valid(2)) + +newZip( + Validated.invalid(NonEmptyArray("Something went wrong.")), + Validated.invalid(NonEmptyArray("Something else went wrong.")) +) + +func newZip(_ a: Func, _ b: Func) -> Func { + return a.flatMap { a in + b.flatMap { b in + Func { _ in (a, b) } + } + } +} + +func newZip(_ a: Parallel, _ b: Parallel) -> Parallel<(A, B)> { + return a.flatMap { a in + b.flatMap { b in + Parallel { callback in callback((a, b)) } + } + } +} + +newZip(delay(by: 2).map { 2 }, delay(by: 3).map { 3 }).run { + print($0) +} + + + + + diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/invoices.json b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/invoices.json new file mode 100644 index 00000000..8c0af97b --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/invoices.json @@ -0,0 +1,14 @@ +[ + { + "amountPaid": 1000, + "amountDue": 0, + "closed": true, + "id": 1 + }, + { + "amountPaid": 500, + "amountDue": 500, + "closed": false, + "id": 2 + } +] diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/user.json b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/user.json new file mode 100644 index 00000000..ae6fc81d --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Resources/user.json @@ -0,0 +1,5 @@ +{ + "email": "blob@pointfree.co", + "id": 42, + "name": "Blob" +} diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Sources/Util.swift b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Sources/Util.swift new file mode 100644 index 00000000..e8ec0129 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/Sources/Util.swift @@ -0,0 +1,17 @@ +precedencegroup EffectfulComposition { + associativity: left + higherThan: AssignmentPrecedence +} + +infix operator >=>: EffectfulComposition + +precedencegroup ForwardComposition { + associativity: left + higherThan: EffectfulComposition +} + +infix operator >>>: ForwardComposition + +public func >>> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C { + return { a in g(f(a)) } +} diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/contents.xcplayground b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/contents.xcplayground new file mode 100644 index 00000000..a93d4844 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..cb4198ae --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..a72dc2b4 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/ManyFacesOfFlatMap.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + \ No newline at end of file diff --git a/0046-the-many-faces-of-flatmap-pt5/Package.resolved b/0046-the-many-faces-of-flatmap-pt5/Package.resolved new file mode 100644 index 00000000..933b69f4 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "NonEmpty", + "repositoryURL": "https://github.com/pointfreeco/swift-nonempty.git", + "state": { + "branch": null, + "revision": "bcf639e8ec6c20b6b7b28ed9bfa0fe83450c3248", + "version": "0.1.2" + } + } + ] + }, + "version": 1 +} diff --git a/0046-the-many-faces-of-flatmap-pt5/Package.swift b/0046-the-many-faces-of-flatmap-pt5/Package.swift new file mode 100644 index 00000000..156e9c16 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:4.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ManyFacesOfFlatMap", + products: [ + // Products define the executables and libraries produced by a package, and make them visible to other packages. + .library( + name: "ManyFacesOfFlatMap", + targets: ["ManyFacesOfFlatMap"]), + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-nonempty.git", from: "0.1.2") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages which this package depends on. + .target( + name: "ManyFacesOfFlatMap", + dependencies: ["NonEmpty"]) + ] +) diff --git a/0046-the-many-faces-of-flatmap-pt5/README.md b/0046-the-many-faces-of-flatmap-pt5/README.md new file mode 100644 index 00000000..4e2fc55c --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/README.md @@ -0,0 +1,14 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [The Many Faces of Flat-Map: Part 5](https://www.pointfree.co/episodes/ep46-the-many-faces-of-flat-map-part-5) +> +> Continuing our 3-part answer to the all-important question "what's the point?", we finally show that standing on the foundation of our understanding of `map`, `zip` and `flatMap` we can now ask and concisely answer very complex questions about the nature of these operations. + +### Getting Started + +* Clone repo +* `cd` into this directory +* Run `swift package generate-xcodeproj` +* Open `ManyFacesOfFlatMap.xcworkspace` +* Build the package for _macOS_ +* Open the playground diff --git a/0046-the-many-faces-of-flatmap-pt5/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift b/0046-the-many-faces-of-flatmap-pt5/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift new file mode 100644 index 00000000..8fcf9749 --- /dev/null +++ b/0046-the-many-faces-of-flatmap-pt5/Sources/ManyFacesOfFlatMap/ManyFacesOfFlatMap.swift @@ -0,0 +1,2 @@ + +let x = 1