Guest User

Untitled

a guest
Mar 21st, 2018
123
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.99 KB | None | 0 0
  1. import UIKit
  2.  
  3. // Copy/paste me into a playground, I'm ready to roll!
  4.  
  5. // We're going to start with UIKit styling functions in the style
  6. // let label = UILabel() |>
  7. // textColor(.blue) >>> backgroundColor(.red)
  8. // then run into difficulties with generifying those functions over protocols,
  9. // and end with an alternative using <> and a nonstandard |>
  10.  
  11. // (My first attempt was a bit messy, but thanks to @stephencelis the final version is pretty ok.)
  12.  
  13. // We'll use the pointfree.co operator definitions:
  14. precedencegroup ForwardApplication {
  15. associativity: left
  16. }
  17.  
  18. infix operator |>: ForwardApplication
  19.  
  20. func |> <A, B>(a: A, f: (A) -> B) -> B {
  21. return f(a)
  22. }
  23.  
  24. precedencegroup ForwardComposition {
  25. associativity: left
  26. higherThan: ForwardApplication
  27. }
  28.  
  29. infix operator >>>: ForwardComposition
  30.  
  31. func >>> <A, B, C>(f: @escaping (A) -> B, g: @escaping (B) -> C) -> ((A) -> C) {
  32. return { a in
  33. g(f(a))
  34. }
  35. }
  36.  
  37. // Now we want to apply styling to UIKit components:
  38. func background(_ color: UIColor) -> (UIView) -> UIView {
  39. return { $0.backgroundColor = color; return $0 }
  40. }
  41.  
  42. func textColor(_ color: UIColor) -> (UILabel) -> UILabel {
  43. return { $0.textColor = color; return $0 }
  44. }
  45.  
  46. // Now, though, we run into type difficulties when we try to compose:
  47.  
  48. let blackAndBlue = textColor(.blue) >>> background(.black) // this order is ok, but...
  49. // let blackAndBlue = background(.black) >>> textColor(.blue)
  50. // error: cannot convert value of type '(UILabel) -> UILabel' to expected argument type '(UIView) -> UILabel'
  51.  
  52. // Generics to the rescue!
  53. func background2<V: UIView>(_ color: UIColor) -> (V) -> V {
  54. return { $0.backgroundColor = color; return $0 }
  55. }
  56.  
  57. let blackAndBlue2 = background2(.black) >>> textColor(.blue)
  58.  
  59. // Now we notice that both UILabel and UITextView have a textColor property,
  60. // with one typed UIColor? and the other UIColor! -- perhaps we can smoothen
  61. // out the annoying difference in optionality, and get a protocol we can reuse
  62. // across both.
  63. // (Let's ignore the question of whether this is a GOOD idea:
  64. // it's just for the sake of a vivid example.)
  65. // We'll also need another, different, styling function to show the problem,
  66. // so let's do the same with a background color property.
  67. protocol ColorSchemeComponentType: AnyObject { // We'll need the AnyObject restriction later, and it certainly does apply to UIKit components
  68. var foreground: UIColor? { get set }
  69. var background: UIColor? { get set }
  70. }
  71. extension UILabel: ColorSchemeComponentType {
  72. var foreground: UIColor? {
  73. get { return textColor }
  74. set { textColor = newValue }
  75. }
  76. var background: UIColor? {
  77. get { return backgroundColor }
  78. set { backgroundColor = newValue }
  79. }
  80. }
  81. extension UITextView: ColorSchemeComponentType {
  82. var foreground: UIColor? {
  83. get { return textColor }
  84. set { textColor = newValue }
  85. }
  86. var background: UIColor? {
  87. get { return backgroundColor }
  88. set { backgroundColor = newValue }
  89. }
  90. }
  91.  
  92. // Now we need styling functions, and we know we should make them generic:
  93. func foreground<A: ColorSchemeComponentType>(_ color: UIColor) -> (A) -> A {
  94. return { $0.foreground = color; return $0 }
  95. }
  96. func background3<A: ColorSchemeComponentType>(_ color: UIColor) -> (A) -> A {
  97. return { $0.background = color; return $0 }
  98. }
  99.  
  100. // Finally, we're ready to show the problem:
  101. // error: binary operator '>>>' cannot be applied to two '(_) -> _' operands
  102. // let headerStyle = foreground(.blue) >>> background3(.red)
  103. // Generic type parameters need to be fully specified when storing into a variable.
  104. // So we can't define a single header style that can be used for both
  105. // UILabel and UITextView (which was the point of extracting the protocol).
  106. let headerStyle: (UILabel) -> UILabel = foreground(.blue) >>> background3(.red)
  107.  
  108. // This greatly restricts our ability to work with these styling functions!
  109.  
  110. // Here's an alternative that works, at the cost of a slightly non-standard |> definition:
  111. func |> <A: AnyObject>(a: A, f: (A) -> Void) -> A {
  112. f(a)
  113. return a
  114. }
  115.  
  116. precedencegroup SingleTypeComposition {
  117. associativity: left
  118. higherThan: ForwardApplication
  119. }
  120.  
  121. infix operator <>: SingleTypeComposition
  122.  
  123. func <> <A>(
  124. f: @escaping (A) -> Void,
  125. g: @escaping (A) -> Void)
  126. -> (A) -> Void {
  127. return { a in
  128. f(a)
  129. g(a)
  130. }
  131. }
  132.  
  133. func foreground2(_ color: UIColor) -> (ColorSchemeComponentType) -> Void {
  134. return { $0.foreground = color }
  135. }
  136. func background4(_ color: UIColor) -> (ColorSchemeComponentType) -> Void {
  137. return { $0.background = color }
  138. }
  139.  
  140. let headerStyle2 = foreground2(.blue) <> background4(.red)
  141.  
  142. // Now this styling function (because it's not generic at all) can be reused
  143. // across all ColorSchemeComponentType-conforming types:
  144. let label: UILabel = UILabel() |> headerStyle2
  145. let textView: UITextView = UITextView() |> headerStyle2
  146.  
  147. // Somewhat to my surprise, these functions even compose with adhoc closures that
  148. // don't reference ColorSchemeComponentType at all:
  149. UILabel() |> headerStyle2 <> { $0.text = "some text" }
Add Comment
Please, Sign In to add comment