Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import UIKit
- // Copy/paste me into a playground, I'm ready to roll!
- // We're going to start with UIKit styling functions in the style
- // let label = UILabel() |>
- // textColor(.blue) >>> backgroundColor(.red)
- // then run into difficulties with generifying those functions over protocols,
- // and end with an alternative using <> and a nonstandard |>
- // (My first attempt was a bit messy, but thanks to @stephencelis the final version is pretty ok.)
- // We'll use the pointfree.co operator definitions:
- precedencegroup ForwardApplication {
- associativity: left
- }
- infix operator |>: ForwardApplication
- func |> <A, B>(a: A, f: (A) -> B) -> B {
- return f(a)
- }
- precedencegroup ForwardComposition {
- associativity: left
- higherThan: ForwardApplication
- }
- infix operator >>>: ForwardComposition
- func >>> <A, B, C>(f: @escaping (A) -> B, g: @escaping (B) -> C) -> ((A) -> C) {
- return { a in
- g(f(a))
- }
- }
- // Now we want to apply styling to UIKit components:
- func background(_ color: UIColor) -> (UIView) -> UIView {
- return { $0.backgroundColor = color; return $0 }
- }
- func textColor(_ color: UIColor) -> (UILabel) -> UILabel {
- return { $0.textColor = color; return $0 }
- }
- // Now, though, we run into type difficulties when we try to compose:
- let blackAndBlue = textColor(.blue) >>> background(.black) // this order is ok, but...
- // let blackAndBlue = background(.black) >>> textColor(.blue)
- // error: cannot convert value of type '(UILabel) -> UILabel' to expected argument type '(UIView) -> UILabel'
- // Generics to the rescue!
- func background2<V: UIView>(_ color: UIColor) -> (V) -> V {
- return { $0.backgroundColor = color; return $0 }
- }
- let blackAndBlue2 = background2(.black) >>> textColor(.blue)
- // Now we notice that both UILabel and UITextView have a textColor property,
- // with one typed UIColor? and the other UIColor! -- perhaps we can smoothen
- // out the annoying difference in optionality, and get a protocol we can reuse
- // across both.
- // (Let's ignore the question of whether this is a GOOD idea:
- // it's just for the sake of a vivid example.)
- // We'll also need another, different, styling function to show the problem,
- // so let's do the same with a background color property.
- protocol ColorSchemeComponentType: AnyObject { // We'll need the AnyObject restriction later, and it certainly does apply to UIKit components
- var foreground: UIColor? { get set }
- var background: UIColor? { get set }
- }
- extension UILabel: ColorSchemeComponentType {
- var foreground: UIColor? {
- get { return textColor }
- set { textColor = newValue }
- }
- var background: UIColor? {
- get { return backgroundColor }
- set { backgroundColor = newValue }
- }
- }
- extension UITextView: ColorSchemeComponentType {
- var foreground: UIColor? {
- get { return textColor }
- set { textColor = newValue }
- }
- var background: UIColor? {
- get { return backgroundColor }
- set { backgroundColor = newValue }
- }
- }
- // Now we need styling functions, and we know we should make them generic:
- func foreground<A: ColorSchemeComponentType>(_ color: UIColor) -> (A) -> A {
- return { $0.foreground = color; return $0 }
- }
- func background3<A: ColorSchemeComponentType>(_ color: UIColor) -> (A) -> A {
- return { $0.background = color; return $0 }
- }
- // Finally, we're ready to show the problem:
- // error: binary operator '>>>' cannot be applied to two '(_) -> _' operands
- // let headerStyle = foreground(.blue) >>> background3(.red)
- // Generic type parameters need to be fully specified when storing into a variable.
- // So we can't define a single header style that can be used for both
- // UILabel and UITextView (which was the point of extracting the protocol).
- let headerStyle: (UILabel) -> UILabel = foreground(.blue) >>> background3(.red)
- // This greatly restricts our ability to work with these styling functions!
- // Here's an alternative that works, at the cost of a slightly non-standard |> definition:
- func |> <A: AnyObject>(a: A, f: (A) -> Void) -> A {
- f(a)
- return a
- }
- precedencegroup SingleTypeComposition {
- associativity: left
- higherThan: ForwardApplication
- }
- infix operator <>: SingleTypeComposition
- func <> <A>(
- f: @escaping (A) -> Void,
- g: @escaping (A) -> Void)
- -> (A) -> Void {
- return { a in
- f(a)
- g(a)
- }
- }
- func foreground2(_ color: UIColor) -> (ColorSchemeComponentType) -> Void {
- return { $0.foreground = color }
- }
- func background4(_ color: UIColor) -> (ColorSchemeComponentType) -> Void {
- return { $0.background = color }
- }
- let headerStyle2 = foreground2(.blue) <> background4(.red)
- // Now this styling function (because it's not generic at all) can be reused
- // across all ColorSchemeComponentType-conforming types:
- let label: UILabel = UILabel() |> headerStyle2
- let textView: UITextView = UITextView() |> headerStyle2
- // Somewhat to my surprise, these functions even compose with adhoc closures that
- // don't reference ColorSchemeComponentType at all:
- UILabel() |> headerStyle2 <> { $0.text = "some text" }
Add Comment
Please, Sign In to add comment