Skip to content

Instantly share code, notes, and snippets.

@ollieatkinson
Last active March 10, 2023 17:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ollieatkinson/9b483c412cdc0bd2b8fa385a84c5dd11 to your computer and use it in GitHub Desktop.
Save ollieatkinson/9b483c412cdc0bd2b8fa385a84c5dd11 to your computer and use it in GitHub Desktop.
Composition of attributes and values for types in Swift
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
}
@ollieatkinson
Copy link
Author

ollieatkinson commented May 22, 2021

let attributes = AttributesOf<UILabel> {
    \.font => .systemFont(ofSize: 10)
    \.text => "Hello World"
}
let label = UILabel()
label += attributes
let label = UILabel() += AttributesOf<UILabel> {
    \.font => .systemFont(ofSize: 10)
    \.text => "Hello World"
}
let label = UILabel() += AttributesOf {
    \.font => .systemFont(ofSize: 10)
    \.text => "Hello World"
}
let label = UILabel() += {
    \.font => .systemFont(ofSize: 10)
    \.text => "Hello World"
}
let font = AttributesOf<UILabel> {
    \.font => .systemFont(ofSize: 10)
}
let color = AttributesOf<UILabel> {
    \.textColor => .tertiaryLabel
    \.backgroundColor => .white
}

let label = UILabel()
label += font + color

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment