|
| 1 | +Design Rationale |
| 2 | +================ |
| 3 | + |
| 4 | +## Why error type isn't generic |
| 5 | + |
| 6 | +```Swift |
| 7 | +enum Event<Element> { |
| 8 | + case Next(Element) // next element of a sequence |
| 9 | + case Error(ErrorType) // sequence failed with error |
| 10 | + case Completed // sequence terminated successfully |
| 11 | +} |
| 12 | +``` |
| 13 | + |
| 14 | +Let's discuss pros and cons of `ErrorType` being generic. |
| 15 | + |
| 16 | +If you have generic error type you create additional impedance mismatch between two observables. |
| 17 | + |
| 18 | +Let's say you have: |
| 19 | + |
| 20 | +`Observable<String, E1>` and `Observable<String, E2>` |
| 21 | + |
| 22 | +There isn't much you can do with them without figuring out what will be the resulting error type. |
| 23 | + |
| 24 | +Will it be `E1`, `E2` or some new `E3` maybe? So you need a new set of operators just to solve that impedance mismatch. |
| 25 | + |
| 26 | +This for sure hurts composition properties, and Rx really doesn't care about why sequence fails, it just forwards failure further. |
| 27 | + |
| 28 | +There is additional problem that maybe in some cases operators will fail for some internal error, and in that case you won't be able to construct resulting error and report failure. |
| 29 | + |
| 30 | +But ok, let's ignore that and assume we can use that to model sequences that don't error out. It looks like it could be useful for that purpose? |
| 31 | + |
| 32 | +Well yes, it potentially could be, but lets consider why would you want to use sequences that don't error out. |
| 33 | + |
| 34 | +One obvious application would be for permanent streams in UI layer that drive entire UI. But when you consider that case, it's not really only sufficient to use compiler to prove that sequences don't error out, you also need to prove other properties. Like that elements are observed on `MainScheduler`. |
| 35 | + |
| 36 | +What you really need is a generic way to prove traits for sequences (`Observables`). And you could be interested in a lot of properties. For example: |
| 37 | + |
| 38 | +* sequence terminates in finite time (server side) |
| 39 | +* sequence contains only one element (if you are running some computation) |
| 40 | +* sequence doesn't error out, never terminates and elements are delivered on main scheduler (UI) |
| 41 | +* sequence doesn't error out, never terminates and elements are delivered on main scheduler, and have refcounted sharing (UI) |
| 42 | +* sequence doesn't error out, never terminates and elements are delivered on specific background scheduler (audio engine) |
| 43 | + |
| 44 | +What you really want is a general compiler enforced system of traits for observable sequences, and a set of invariant operators for those wanted properties. |
| 45 | + |
| 46 | +A good analogy IMHO would be |
| 47 | + |
| 48 | +``` |
| 49 | +1, 3.14, e, 2.79, 1 + 1i <-> Observable<E> |
| 50 | +1m/s, 1T, 5kg, 1.3 pounds <-> Errorless observable, UI observable, Finite observable ... |
| 51 | +``` |
| 52 | + |
| 53 | +There are many ways how to do that in Swift by either using composition or inheritance of observables. |
| 54 | + |
| 55 | +Additional benefit of using unit system is that you can prove that UI code is executing on same scheduler and thus use lockless operators for all transformations. |
| 56 | + |
| 57 | +Since Rx already doesn't have locks for single sequence operations, and all of the remaining locks are in statefull components (aka UI), that would practically remove all of the remaining locks out of Rx code and create compiler enforced lockless Rx code. |
| 58 | + |
| 59 | +So IMHO, there really is no benefit of using typed Errors that couldn't be achieved cleaner in other ways while preserving Rx compositional semantics. And other ways also have huge other benefits. |
| 60 | + |
| 61 | +## Pipe operator |
| 62 | + |
| 63 | +This is the definition of `>-` operator. |
| 64 | + |
| 65 | +```swift |
| 66 | +func >- <In, Out>(lhs: In, rhs: In -> Out) -> Out { |
| 67 | + return rhs(lhs) |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +This enables us to write |
| 72 | + |
| 73 | +```swift |
| 74 | +a >- map { $0 * 2 } >- filter { $0 > 0 } |
| 75 | +``` |
| 76 | + |
| 77 | +instead of |
| 78 | + |
| 79 | +```swift |
| 80 | +a.map { $0 * 2 }.filter { $0 > 0 } |
| 81 | +``` |
| 82 | + |
| 83 | +This is another explanation: |
| 84 | + |
| 85 | +```swift |
| 86 | +a >- b >- c is equivalent to c(b(a)) |
| 87 | +``` |
| 88 | + |
| 89 | +So why was this introduced and not just use "." and extensions? Short answer is that Swift extensions weren't powerful enough, but there are other reasons as well. |
| 90 | + |
| 91 | +Next version of RxSwift for Swift 2.0 will probably also include extensions that will enable the use of |
| 92 | +`.`. |
| 93 | + |
| 94 | +">-" also enables us to chain results easily. For example, if using protocol extensions typical example would look like this. |
| 95 | + |
| 96 | +```swift |
| 97 | +disposeBag.addDisposable( |
| 98 | + observable |
| 99 | + .map { n in |
| 100 | + n * 2 |
| 101 | + } |
| 102 | + .subscribeNext { n in |
| 103 | + print(n) |
| 104 | + } |
| 105 | + ) |
| 106 | +``` |
| 107 | + |
| 108 | +This code could be written more elegantly using `>-` operator. |
| 109 | + |
| 110 | +```swift |
| 111 | +observable |
| 112 | + >- map { n in |
| 113 | + n * 2 |
| 114 | + } |
| 115 | + >- subscribeNext { n in |
| 116 | + print(n) |
| 117 | + } |
| 118 | + >- disposeBag.addDisposable |
| 119 | +``` |
| 120 | + |
| 121 | +All of the Rx public interfaces don't depend at all on the `>-` operator. |
| 122 | + |
| 123 | +It was actually introduced quite late and you can use Rx operators (map, filter ...) without it. |
| 124 | + |
| 125 | +If you dislike `>-` operator and want to use `|>` or `~>` operators, just define them in your project in this form: |
| 126 | + |
| 127 | +```swift |
| 128 | +infix operator |> { associativity left precedence 91 } |
| 129 | + |
| 130 | +public func |> <In, Out>(source: In, @noescape transform: In -> Out) -> Out { |
| 131 | + return transform(source) |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +or |
| 136 | + |
| 137 | +``` |
| 138 | +infix operator ~> { associativity left precedence 91 } |
| 139 | +
|
| 140 | +public func ~> <In, Out>(source: In, @noescape transform: In -> Out) -> Out { |
| 141 | + return transform(source) |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +and you can use them instead of `>-` operator. |
| 146 | + |
| 147 | +```swift |
| 148 | +let a /*: Observable<Int>*/ = Variable(1) |
| 149 | +let b /*: Observable<Int>*/ = Variable(2) |
| 150 | + |
| 151 | +combineLatest(a, b) { $0 + $1 } |
| 152 | + |> filter { $0 >= 0 } |
| 153 | + |> map { "\($0) is positive" } |
| 154 | + |> subscribeNext { println($0) } |
| 155 | +``` |
| 156 | + |
| 157 | +```swift |
| 158 | +let a /*: Observable<Int>*/ = Variable(1) |
| 159 | +let b /*: Observable<Int>*/ = Variable(2) |
| 160 | + |
| 161 | +combineLatest(a, b) { $0 + $1 } |
| 162 | + ~> filter { $0 >= 0 } |
| 163 | + ~> map { "\($0) is positive" } |
| 164 | + ~> subscribeNext { println($0) } |
| 165 | +``` |
| 166 | + |
| 167 | +So why was `>-` chosen in the end? Well, it was a difficult decision. |
| 168 | + |
| 169 | +Why wasn't standard function application operator used? |
| 170 | + |
| 171 | +I've first tried to find a similar operator in swift core libraries, but couldn't find it. That meant that I'll need to define something myself or find some third party library that contains reference function application operator definition and use it. |
| 172 | +Otherwise all of the example code would be unreadable. |
| 173 | + |
| 174 | +Why wasn't some standard library used for that operator? |
| 175 | + |
| 176 | +Well, I'm not sure there is a clear consensus in the community about funtion application operators or libraries that define them. |
| 177 | + |
| 178 | +Why wasn't function application operator defined only for `Observables` and `Disposables`? |
| 179 | + |
| 180 | +One of the solutions could have been to provide a specialized operator that just works for `Observables` and `Disposables`. |
| 181 | +In that case, if an identically named general purpose function application operator is defined somewhere else, there would still be collision, priority or ambiguity problems. |
| 182 | + |
| 183 | +Why wasn't some more standard operator like `|>` or `~>` used? |
| 184 | + |
| 185 | +`|>` or `~>` are probably more commonly used operators in swift, so if there was another definition for them in Rx as general purpose function application operators, there is a high probability they would collide with definitions in other frameworks or project. |
| 186 | + |
| 187 | +The simplest and safest solution IMHO was to create some new operator that made sense in this context and there is a low probability anyone else uses it. |
| 188 | +In case the operator naming choice was wrong, name is rare and community eventually reaches consensus on the matter, it's more easier to find and replace it in user projects. |
| 189 | + |
| 190 | +I have experimented for a week with different operators and in the end these are the reasons why `>-` was chosen |
| 191 | + |
| 192 | +* It's short, only two characters |
| 193 | +* It looks like a sink to the right, which is a function it actually performs, so it's intuitive. |
| 194 | +* It doesn't create a lot of visual noise. `|>` compared to `>-` IMHO looks a lot more intrusive. When my visual cortex parses `|>` it creates an illusion of a filled triangle, and when it parses `>-`, it sees three lines that don't cover any surface area, but are easily recognizable. Of course, that experience can be different for other people, but since I really wanted to create something that's pleasurable for me to use, that's a good argument. I'm just hoping that other people have the same experience. |
| 195 | +* In the worst case scenario, if this operator is awkward to somebody, they can easily replace it using instructions above. |
0 commit comments