Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Great Swiftening (a.k.a. the new 3.0) #1382

Merged
merged 1,645 commits into from Jul 19, 2015
Merged

The Great Swiftening (a.k.a. the new 3.0) #1382

merged 1,645 commits into from Jul 19, 2015

Conversation

jspahrsummers
Copy link
Member

Resolves #1365 and #1366. Per my comments in #1369, this is intended to be a long-running branch that will eventually become RAC 3.0, supplanting the current 3.0-development work.

To get us started, I basically just subtree merged jspahrsummers/RxSwift into here, then bridged several RAC classes back and forth.

@robrix
Copy link
Contributor

robrix commented Jun 19, 2014

We may need to revamp our build settings in order to combine Swift and Objective-C correctly.

  1. May Gord have mercy on your soul.
  2. Just 🔥 the Objective-C stuff, c’mon.

import Foundation

/// An immutable wrapper that can turn any value into an object.
class Box<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotate with @final for great good. Works in beta2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a mutable subclass of this type (mostly useful for arrays and a consistent notion of identity).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use a protocol. 😉 FP Nah, it's all good.

///
/// This will be non-nil while executing, nil between executions, and will
/// only update on the main thread.
var executions: Observable<Observable<Result<O>?>?> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't the Observable's inner value be Result<O>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this new definition, Observables must always have a current value (they cannot be empty). Since the inner observables are sent along when execution starts, we don't know the result yet, and thus we have to allow them to be nil.

@jspahrsummers
Copy link
Member Author

This latest refactoring introduces Enumerable Producer and Observable Signal types that differ from my previous proposals:

  • Enumerable<T> Producer<T> is pretty boring—it's basically just a cold RACSignal.
  • Observable<T> Signal<T> is like a hot signal, except that it has no concept of completing or erroring, and must always have a value (it can never be empty)—very much like Elm's Signal type, or “behaviors” in other FRP formulations.

You can intuitively think of an Signal as being a property over time, without much else, but there are some very interesting things that come out of this definition. In particular, the new Promise<T> type is like a Signal<T?>, where the current value is nil before it's been resolved.

I've also introduced an Action type, which is very similar to the RACAction class that was built to replace RACCommand.

Here's the best part: all of this stuff is bridged to RAC 2.x concepts, so you should be able to try this out in a Swift project today (though the usual disclaimers apply about not being production-ready, and me possibly breaking everything in the future).

Let me know what you think. ❤️

@KyleLeneau
Copy link
Contributor

Is the iOS target (static library) expected to build yet? Getting a weird linker error unknown option character X' in: -Xlinker`.

@jspahrsummers
Copy link
Member Author

@KyleLeneau Nope, barely any time has been invested in the build process right now.

@cwharris
Copy link

cwharris commented Jul 3, 2014

I've been trying to hold my tongue on the issue of naming conventions in RAC + RxSwift for over year now. I figured Objective-C is different enough to possibly need its own naming, simply because of namespacing, or protocal/implementation conflicts while adhering to a Apple coding guidelines. Basically, I have no idea.

That being said, RxSwift doesn't fall prey to namespacing issues, and it's a new language, so interface/protocol naming conventions could (maybe not should, though) be overridden without too much of a pounding from our Apple overlords.

It should also be said that, while I have plenty of experience with Rx in general, I have very little experience with RAC, Objective-C, or Swift (although I hope to become more familiar with Swift sooner rather than later).

My main concern on this is with the matter consistency. My second concern is with general accuracy. By second hand experience, I would suspect the order of these concerns are reversed in your case, and I believe this reversal is a product of a shared goal: to let developers think quickly about Rx.

That being said, I disagree with many of the RAC naming conventions.

Observable, for instance, is a clearly defined pattern by which a consumer can indicate to a producer that it would like to receive that which the producer produces. The nature of this subscription process has traditionally been, for the most part, side-effect free. I'd like to believe that Rx started this way, but eventually realized the true potential of the pattern: Dynamically Generated Asynchronous Functions with Multiple Return Values.

Perhaps the name of Observable should have been changed to DynamicAsyncMultiRetFunc, or Functoree, or something equally nondescript. Never-the-less, the name was not changed, and many a years has the name been Observable. That is, except within the confines of RAC.

Unless explicitly mentioned, one would have a difficult time determining that RAC were based on Rx, or even inspired by, for that matter. Furthermore, it would be more difficult to come from a world of Rx and attempt to work in the world of RAC. The brain can only hold so much information, and if one wishes to work in both of these worlds, it requires twice as much memory consumption. Or at least, twice as many hash keys (or whatever it is we use for lookups in that big grey thing we think with).

Therefore, by potentially reducing the barrier of entry to RAC from the outside world via a proprietary and potentially more accurate naming convention, the solution itself has definitely increased the barrier of entry to RAC from the Rx world. From which, it could be, many RAC users emerge. The exclusivity works both ways, however. The barrier from Rx to RAC is the same as the barrier from RAC to Rx, by which new RAC users are discouraged from using Rx learning materials, as they do not map one-to-one with the RAC naming conventions. Worse, some of the conventions are contrary.

If any custom naming conventions are definitively or majorly clearer than the historical Rx naming conventions, then the difference is not simply justified, but demanded. Not only for RAC, but for Rx in general. However, if this is not the case, I argue that the difference between the two can only serve to compound the confusion and increase the cognitive overhead working with Rx. A few days of waking up on the opposite side of the bed tends to cause the new side to feel natural.

I digress.

@cwharris
Copy link

cwharris commented Jul 3, 2014

My concrete concerns with this particular pull request are as follows:

  • Is Enumerable<T> truly an Enumerable? Does it operate in a pull-based manner, or is it truly "basically just a cold RACSignal"
  • Observable<T> Is already in use in Rx, and it therefore seems contrary to use it here in a different manner. Why not HotRACSignal or BehaviorSubject? Also, can we even constrain this mechanism without a much stronger type system, or is this type simply waiting to be misused?

@anaisbetts
Copy link

My concrete concerns with this particular pull request are as follows:

Yeah, naming these the same as established Rx names but having fundamentally different semantics is a pretty big troll for people coming from Google Searches. At least use "Sequence" and "Signal" so they don't conflict

@jspahrsummers
Copy link
Member Author

@cwharris Thanks for writing up your thoughts and posting them here! I'll respond to each of your points in turn:

I figured Objective-C is different enough to possibly need its own naming, simply because of namespacing, or protocal/implementation conflicts while adhering to a Apple coding guidelines.

We've historically pursued these goals for naming (in roughly this order):

  1. Simple
  2. Easily understood by users who have no prior experience with RAC or Rx
  3. Aligned with Cocoa convention
  4. Aligned with Rx convention

I would venture to say that most RAC users do not have familiarity with Rx, and do not particularly care whether their skills transfer between the two, so a lower barrier-to-entry for the typical Cocoa programmer has always been more important to us.

Observable, for instance, is a clearly defined pattern by which a consumer can indicate to a producer that it would like to receive that which the producer produces. The nature of this subscription process has traditionally been, for the most part, side-effect free.

The Observer pattern is very familiar to most developers, and this is precisely the problem. They are familiar with “observation” as something that does not cause side effects.

I strongly dislike overloading the term for a stream that causes stuff to happen on observation (and it's not even predictable, given the existence of hot and "warm" observables, which use the same naming).

Unless explicitly mentioned, one would have a difficult time determining that RAC were based on Rx, or even inspired by, for that matter. Furthermore, it would be more difficult to come from a world of Rx and attempt to work in the world of RAC. The brain can only hold so much information, and if one wishes to work in both of these worlds, it requires twice as much memory consumption.

As mentioned above, this is one of the least important goals for us. We do care about it, but we care about other stuff—like The Right Way™ to do things—way more. If we think that we can do something better than Rx, we will (however small or large the change).

However,

some of the conventions are contrary.

This is definitely a huge problem, because fake familiarity is more confusing than the unfamiliar.

I would love to change the names used here, but I haven't yet come up with anything better. It should definitely happen before this is sanctioned as production-ready, though.

Is Enumerable truly an Enumerable? Does is operate in a pull-based manner, or is it truly "basically just a cold RACSignal"

The latter. This is why Sequence is not a great name either.

Observable Is already in use in Rx, and it therefore seems contrary to use it here in a different manner. Why not just HotRACSignal?

I would greatly prefer a name that clearly conveys its meaning to reader, without any need to look up documentation.

"Hot" and "cold" are the most confusing concepts in Rx and RAC right now, so I don't think it's helpful to make reference to them. This is exactly the problem I'm trying to solve—codifying the distinction in a more helpful way that's less prone to misunderstandings and misuse.

At least use "Sequence" and "Signal" so they don't conflict

@paulcbetts As mentioned above, Sequence isn't a great fit for something that's not really pull-driven.

I'm also not a big fan of Signal for what we have here, because I've been conceptualizing it as a property over time—it always has a current value, and you can observe changes to that value. Observable would be perfect if it weren't already overloaded. 😕

@jspahrsummers
Copy link
Member Author

Sorry, missed this one about Observable:

can we even constrain this without a much stronger type system?

@cwharris The current implementation of Observable does allow side effects to be injected, but they're always performed as values are sent—regardless of how many observers there are.

In other words, the closure passed to the constructor is executed immediately, and only once, making it mostly impossible to implement “cold” behaviors.

@cwharris
Copy link

cwharris commented Jul 3, 2014

I would venture to say that most RAC users do not have familiarity with Rx

That's exactly my point. What if you have a lack of experienced RAC users because the transition from Rx imparts a large cognitive overhead? That, and Objective-C was the main barrier to entry into the RAC world. Now that Swift exists, there's the potential for a lot more cross-over.

I would greatly prefer a name that clearly conveys its meaning to reader, without any need to look up documentation.

I actually edited my answer after you quoted some of it. I also suggest BehaviorSubject instead of Observable, because this is a one-to-one, and historically accurate, convention.

The Right Way™ to do things

Picard can portray my feelings more accurately than I, in this instance.

Alt text

@jspahrsummers
Copy link
Member Author

I don't think “behavior” is very clear to anyone without FRP or Rx experience. I may revert to “signal” (which, incidentally, behaviors have been referred to as) because it's the least-bad option I can see.

Still not sure what to do for the cold stream of events.

jspahrsummers added a commit that referenced this pull request Jul 3, 2014
@cwharris
Copy link

cwharris commented Jul 3, 2014

Behaviors and signals are two separate ideas. A signal is an event, and a behavior is a value which changes over time.

I've seen ObservableValue used for something like this, too. Maybe that's clearer?

@jspahrsummers
Copy link
Member Author

A signal is an event

That's only true in Rx. It refers to behaviors or discrete event streams in every other system, including Elm and most formulations of arrowized FRP.

@jlawton
Copy link
Contributor

jlawton commented Jul 3, 2014

I do not come from Rx, and I can confirm that Signal is often a synonym for Behavior, which represents a value at all points in time. Events usually represent something which has a value only at discrete moments.

Most systems implement Signals as discretely changing, but still the Signal represents a value at all moments in time, even between the updates.

Some systems also let you map over the updates of a Signal's value as if it were an Event, but it is still not an Event.

@erikprice
Copy link

Bacon.js uses the terms EventStream and Property for its hot signals. It
has no equivalent of cold signals. (A Property is semantically equivalent
to the signal returned from RACObserve().)

On Thursday, July 3, 2014, jlawton notifications@github.com wrote:

I do not come from Rx, and I can confirm that Signal is often a synonym
for Behavior, which represents a value at all points in time. Events
usually represent something which has a value only at discrete moments.

Most systems implement Signals as discretely changing, but still the
Signal represents a value at all moments in time, even between the updates.

Some systems also let you map over the updates of a Signal's value as if
it were an Event, but it is still not an Event.


Reply to this email directly or view it on GitHub
#1382 (comment)
.

@Coneko
Copy link
Member

Coneko commented Jul 5, 2014

I understand "signal" as @jspahrsummers: something that is either a behaviour or an event stream.

I think it's often regarded as a synonym for one or the other because the difference between the two is not widely understood.

A behaviour is a value that changes over time: a value is defined at all times after the behaviour "starts existing".

An event stream is a collection of events over time. Events can occur at any time after the stream has been created, including never.

The two concepts are very different, but they are connected: any behaviour can be converted in an event stream by considering changes in value as events, and any event stream can be converted in a behaviour by calculating a value based on the events that have happened so far.

Because the most common use cases for both have to do with discrete time rather than continuous time the conversion between the two is very natural, which confuses the two.

In RAC3 behaviours are represented by ObjC properties, event streams are represented as signals. Note that it does not provide any way of working with behaviours directly, only ways to convert them to and from event streams.

Note that all of this still doesn't make any distinction between hot and cold or pull and push.

@Coneko
Copy link
Member

Coneko commented Jul 5, 2014

Producer: Event stream, push based, hot or cold.
Signal: Behaviour, push based, hot.

Looks good so far, but I have a big change to request: just like Producer doesn't actually do the producing itself, but is actually a factory of things that produce, I'd like Consumer to be a factory of things that consume rather than for it to do the consuming.

This would help keeping the interactions between the parts of the RAC system within RAC's internals, allowing us to change the internal assumptions about the system without touching the external semantics.

For example, with Consumer specified as it is now, it can never make any assumptions on how put is going to be called on it, as it could be called not only by the Producer it's consuming, but also by user code at any time, from any thread.

This was referenced Jul 7, 2014
@jspahrsummers
Copy link
Member Author

Producer: Event stream, push based, hot or cold.

@Coneko FWIW, my goal is that this type should always be cold (in the sense of, “different observers may see a different version or timing of events”) or warm. At the very least, we shouldn't include any features that make it possible to use differently.

I'd like Consumer to be a factory of things that consume rather than for it to do the consuming.

I could see an argument for making put private, but what does the factory-ness add? Seems like it could result in really unexpected behavior if the passed-in closure is evaluated for multiple streams (serially or concurrently).

@jspahrsummers jspahrsummers changed the title [WIP] The Great Swiftening (a.k.a. the new 3.0) The Great Swiftening (a.k.a. the new 3.0) Jul 19, 2015
@jspahrsummers
Copy link
Member Author

jspahrsummers added a commit that referenced this pull request Jul 19, 2015
The Great Swiftening (a.k.a. the new 3.0)
@jspahrsummers jspahrsummers merged commit 6c37b5e into master Jul 19, 2015
@jspahrsummers jspahrsummers deleted the swift-development branch July 19, 2015 04:43
This was referenced Jul 19, 2015
@kastiglione
Copy link
Member

swift-clapping

@jspahrsummers
Copy link
Member Author

I mentioned it on Twitter, but just to reiterate it here:

@ReactiveCocoa/reactivecocoa Thank you all (and all of our other contributors outside of the org) for everything you’ve done to help RAC 3 along! It definitely wouldn’t have been possible without you. ❤️

@anaisbetts
Copy link

👏👏👏👏👏👏👏👏👏👏👏👏

@rhysforyou
Copy link
Contributor

👏 🎆 🎉

@joshaber
Copy link
Member

@robb
Copy link
Member

robb commented Jul 20, 2015

💖

@wajatimur
Copy link

Yeay, Thanks Buds.. I've waiting for this very long..

@haaakon
Copy link

haaakon commented Jul 23, 2015

👍

@damianesteban
Copy link

This is very exciting stuff :) Should installing RAC with Carthage download v3.0? It is currently download v2.5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ReactiveSwift