diff --git a/0087-the-case-for-case-paths-pt1/CasePath.playground/Contents.swift b/0087-the-case-for-case-paths-pt1/CasePath.playground/Contents.swift new file mode 100644 index 00000000..9831cfde --- /dev/null +++ b/0087-the-case-for-case-paths-pt1/CasePath.playground/Contents.swift @@ -0,0 +1,142 @@ +import Foundation + +struct User { + var id: Int + let isAdmin: Bool + var name: String +} + +\User.id as WritableKeyPath +\User.isAdmin as KeyPath +\User.name + +var user = User(id: 42, isAdmin: true, name: "Blob") + +user[keyPath: \.id] + +user[keyPath: \.id] = 57 + +user.id = 57 +user.name = "Blob Jr." + +class Label: NSObject { + @objc dynamic var font = "Helvetica" + @objc dynamic var fontSize = 12 + @objc dynamic var text = "" +} + +class Model: NSObject { + @objc dynamic var userName = "" +} + +let model = Model() +let label = Label() + +bind(model: model, \.userName, to: label, \.text) + +//bind(model: model, get: { $0.userName }, to: label, get: { $0.text }, set: { $0.text = $1 }) + +label.text +model.userName = "blob" +label.text +model.userName = "XxFP_FANxX93" +label.text + +import Combine + +let subject = PassthroughSubject() +subject.assign(to: \.text, on: label) + +subject.send("MaTh_FaN96") +label.text + + +typealias Reducer = (inout Value, Action) -> Void + +[1, 2, 3] + .reduce(into: 0, { $0 += $1 }) +[1, 2, 3] + .reduce(into: 0, +=) + + +func pullback( + _ reducer: @escaping Reducer, + value: WritableKeyPath +) -> Reducer { + + return { globalValue, action in +// var localValue = globalValue[keyPath: value] +// reducer(&localValue, action) +// globalValue[keyPath: value] = localValue + reducer(&globalValue[keyPath: value], action) + } +} + +func pullback( + _ reducer: @escaping Reducer, + getLocalValue: @escaping (GlobalValue) -> LocalValue, + setLocalValue: @escaping (inout GlobalValue, LocalValue) -> Void +) -> Reducer { + + return { globalValue, action in + var localValue = getLocalValue(globalValue) + reducer(&localValue, action) + setLocalValue(&globalValue, localValue) + } +} + +let counterReducer: Reducer = { count, _ in count += 1 } + +pullback(counterReducer, value: \User.id) + +[1, 2, 3] + .map(String.init) + +pullback( + counterReducer, + getLocalValue: { (user: User) in user.id }, + setLocalValue: { $0.id = $1 } +) + + +struct _WritableKeyPath { + let get: (Root) -> Value + let set: (inout Root, Value) -> Void +} + + +// [user valueForKeyPath:@"location.city"] + +struct CasePath { + let extract: (Root) -> Value? + let embed: (Value) -> Root +} + +extension Result { + static var successCasePath: CasePath { + CasePath( + extract: { result -> Success? in + if case let .success(value) = result { + return value + } + return nil + }, + embed: Result.success + ) + } + + static var failureCasePath: CasePath { + CasePath( + extract: { result -> Failure? in + if case let .failure(value) = result { + return value + } + return nil + }, + embed: Result.failure + ) + } +} + +Result.successCasePath +Result.failureCasePath diff --git a/0087-the-case-for-case-paths-pt1/CasePath.playground/Sources/Bind.swift b/0087-the-case-for-case-paths-pt1/CasePath.playground/Sources/Bind.swift new file mode 100644 index 00000000..66bd6fda --- /dev/null +++ b/0087-the-case-for-case-paths-pt1/CasePath.playground/Sources/Bind.swift @@ -0,0 +1,15 @@ +import Foundation + +public func bind( + model: Model, + _ modelKeyPath: KeyPath, + to target: Target, + _ targetKeyPath: ReferenceWritableKeyPath +) { + var observation: NSKeyValueObservation! + observation = model.observe(modelKeyPath, options: [.initial, .new]) { _, change in + guard let newValue = change.newValue else { return } + target[keyPath: targetKeyPath] = newValue + _ = observation + } +} diff --git a/0087-the-case-for-case-paths-pt1/CasePath.playground/contents.xcplayground b/0087-the-case-for-case-paths-pt1/CasePath.playground/contents.xcplayground new file mode 100644 index 00000000..63b6dd8d --- /dev/null +++ b/0087-the-case-for-case-paths-pt1/CasePath.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/0087-the-case-for-case-paths-pt1/README.md b/0087-the-case-for-case-paths-pt1/README.md new file mode 100644 index 00000000..600a994b --- /dev/null +++ b/0087-the-case-for-case-paths-pt1/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [The Case for Case Paths: Introduction](https://www.pointfree.co/episodes/ep86-swiftui-snapshot-testing) +> +> You've heard of key paths, but…case paths!? Today we introduce the concept of "case paths," a tool that helps you generically pick apart an enum just like key paths allow you to do for structs. It's the tool you never knew you needed. diff --git a/README.md b/README.md index f5ea08ba..7f859ea7 100644 --- a/README.md +++ b/README.md @@ -88,4 +88,5 @@ This repository is the home of code written on episodes of 1. [Effectful State Management: Effects](0083-testable-state-management-effects) 1. [Effectful State Management: Ergonomics](0084-testable-state-management-ergonomics) 1. [Effectful State Management: The Point](0085-testable-state-management-the-point) -1. [Effectful State Management: The Point](0086-swiftui-snapshot-testing) +1. [SwiftUI Snapshot Testing](0086-swiftui-snapshot-testing) +1. [The Case for Case Paths: Introduction](0087-the-case-for-case-paths-pt1)