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