Last active
March 10, 2023 17:41
-
-
Save ollieatkinson/9b483c412cdc0bd2b8fa385a84c5dd11 to your computer and use it in GitHub Desktop.
Composition of attributes and values for types in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct AttributesOf<Object>: CustomStringConvertible { | |
enum Error: Swift.Error { case message(String) } | |
public typealias Assignment = (keyPath: AnyKeyPath, set: (Object) -> Void) | |
private(set) var assignments: [Assignment] | |
init(@AttributesOfBuilder<Object> _ builder: () throws -> [Assignment]) rethrows { | |
assignments = try builder() | |
} | |
private init( _ o: [Assignment]) { | |
self.assignments = o | |
} | |
func unsafeCast<T>(to type: T.Type = T.self) -> AttributesOf<T> { | |
precondition(T.self is Object.Type, "\(Object.self) is not a superclass of \(T.self)") | |
return AttributesOf<T> { | |
assignments.map { ø in (ø.keyPath, { ø.set(unsafeBitCast($0, to: Object.self)) }) } | |
} | |
} | |
func cast<T>(to type: T.Type = T.self) throws -> AttributesOf<T> { | |
guard T.self is Object.Type else { throw Error.message("\(Object.self) is not a superclass of \(T.self)") } | |
return unsafeCast(to: T.self) | |
} | |
func appending(_ attributes: AttributesOf<Object>) -> AttributesOf<Object> { | |
AttributesOf(assignments + attributes.assignments) | |
} | |
func apply(on object: Object) { | |
for assignment in assignments { | |
assignment.set(object) | |
} | |
} | |
var description: String { "AttributesOf<\(Object.self)" } | |
} | |
@resultBuilder | |
struct AttributesOfBuilder<Object> { | |
public typealias Component = [AttributesOf<Object>.Assignment] | |
@inlinable public static func buildBlock() -> Component { [] } | |
@inlinable public static func buildBlock(_ component: Component) -> Component { component } | |
@inlinable public static func buildBlock(_ component: Component...) -> Component { component.flatMap{ $0 } } | |
@inlinable public static func buildArray(_ components: [Component]) -> Component { components.flatMap { $0 } } | |
@inlinable public static func buildOptional(_ component: Component?) -> Component { component ?? [] } | |
@inlinable public static func buildEither(first child: Component) -> Component { child } | |
@inlinable public static func buildEither(second child: Component) -> Component { child } | |
@inlinable public static func buildExpression(_: Void) -> Component { [] } | |
@inlinable public static func buildExpression(_ expression: Component) -> Component { expression } | |
@inlinable public static func buildExpression(_ expression: AttributesOf<Object>) -> Component { expression.assignments } | |
@inlinable public static func buildExpression<T>(_ expression: @autoclosure @escaping () throws -> AttributesOf<T>) throws -> Component { try expression().cast(to: Object.self).assignments } | |
@inlinable public static func buildExpression(_ expression: AttributesOf<Object>.Assignment...) -> Component { expression } | |
@inlinable public static func buildExpression<Value>(_ keyPath: KeyPath<Object, Value>) -> Component { [(keyPath, { _ in })] } | |
} | |
infix operator =>: AssignmentPrecedence | |
func => <Root, Value>(keyPath: ReferenceWritableKeyPath<Root, Value?>, value: Value) -> AttributesOf<Root>.Assignment { | |
(keyPath: keyPath, set: { $0[keyPath: keyPath] = value }) | |
} | |
func => <Root, Value>(keyPath: ReferenceWritableKeyPath<Root, Value>, value: Value) -> AttributesOf<Root>.Assignment { | |
(keyPath: keyPath, set: { $0[keyPath: keyPath] = value }) | |
} | |
@discardableResult | |
func += <Object> (lhs: Object, rhs: AttributesOf<Object>) -> Object { | |
rhs.apply(on: lhs) | |
return lhs | |
} | |
@discardableResult | |
func += <Object> (lhs: Object, @AttributesOfBuilder<Object> _ rhs: () throws -> [AttributesOf<Object>.Assignment]) rethrows -> Object { | |
try AttributesOf(rhs).apply(on: lhs) | |
return lhs | |
} | |
Author
ollieatkinson
commented
May 22, 2021
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment