Comments (11)
This makes Action
s operate on immutable snapshots of the input, and aligns better with the spirits of FP.
The downside is, as you might have implied, that it is less ideal as a bridge to the imperative world.
Edit:
Moreover, this might limit the applicability of Action
, I'd say. Say if we have an action that is expected to be one-to-one in input and output, are we supposed to guarantee the order of apply
and the input source among multiple clients?
e.g.
// Thread 1
coordinator.nextOpenToken.value = tokenA
coordinator.open.apply().startWithFailed { error in
// prompt user about the error.
}
// Thread 2
coordinator.nextOpenToken.value = tokenB
coordinator.open.apply() .startWithFailed { error in
// prompt user about the error.
}
from reactiveswift.
This definitely limits the applicablility of Action
. The question is whether the eliminated applications are useful and good, or whether this clarifies the role of actions.
from reactiveswift.
One question though - would apply
capture the input at its call time, or would apply
return a producer that operates on the current value when it starts?
In the later case, apply
might not necessary return a producer, but start the execution immediately, since we no longer need to inject the input as what apply
meant to do.
(It seems to be starting immediately as apply
was said to be bindable to trigger signals)
I don't know how to describe it precisely. Just trying my best.
What's eliminated are those having multiple input sources competing for execution.
The entire bloc of a form UI can be treated as the sole and only input source of the view model. So Action
in the proposal for view models in such cases is perfectly fine.
But for the example I put above*, there are multiple concurrent clients that want to execute the action with the value they proposed. With this proposal, there is no definitive way to:
- ensure atomicity of input-then-execution, as clients could overwrite each other's input; and
- differentiate their results from each other, via the producer returned by the old
apply
.
The proposal axes these semantics, in order to eliminate the race condition between the execution closure and the enabling state. But the said race condition is probably relevant to just UI programming, as I feel the use of it in the model layer (or anything outside the VM-View bindings) would most likely be a plain stored boolean.
Nonetheless, the proposed API does feel better for UI programming IMO. There is no better alternative either, assuming thread containment is always off the table.
* That is an action, hosted in the app coordinator, to handle opening of a new document (window). That's said I could instead turn it back being imperative, and move the action (which now calls the imperative version when executed) to the view model to fit this new semantic of Action
.
from reactiveswift.
I don't know how widely-used Action
is. To me, it feels harder to use than it should be (in the sense that it's super useful and things that are super useful should also be easy to use).
Here's what I get out of it:
- Serialisation: I know that when the action's producer is executing, it is the only one executing. I can trust that side effects won't be executed more than I expect.
- Command-Query Separation: Signal producers execute their side effects for every observer, and so it can become quite difficult to reason about what side effects will occur, and when. Actions give me a way to separate the work to be done from observing the progress or effect of that work.
On the other hand, there's a really great symmetry between these two type signatures:
let execute: (Input) -> SignalProducer<Output, Error>
let task: Action<Input, Output, Error>
task = Action(execute)
I wonder if there's two different concepts here? And maybe one could be composed from the other?
I'm still trying to think this through...
from reactiveswift.
Action
is a concept super useful and powerful to me especially, when used at UI layer.
But this does require (1) removing the input type from Action and (2) removing the input parameter from apply(). (2) could be especially problematic, depending on how people are currently using Action. But in the standard bind-this-action-to-a-button case, this model seems like it would be simpler.
@mdiep I like how your proposal aims for immutability which is definitely a good thing. Unfortunately, personally speaking about my use of actions, I have a few cases where the input is dynamic, e.g. according to the value of a UISwitch
, and others where my enabledIf
property is not related at all with my input.
But doing a more philosophical analysis, can we really avoid this kind of bad usage? I mean, this definetly makes it more harder but it's still possible to ignore the input and access a shared state while defining the executing block (which can be the body of a SignalProducer
).
But I'm still reflecting about this, it's definitely not an easy one.
from reactiveswift.
Unfortunately, personally speaking about my use of actions, I have a few cases where the input is dynamic, e.g. according to the value of a UISwitch
You could still achieve that.
let switch = UISwitch()
let property = Property<Bool>(value: false, then: switch.reactive.isOnValues)
let action = Action(input) { ... }
from reactiveswift.
We use the RAC2 equivalent of Action
(RACCommand
) extensively in our ReactiveObjC codebase, so I think I can speak to our usage of RACCommand
's enabled
property and how we tend to use it in practice.
In cases where an Action's enabled-ness depends exclusively on its inputs, a pattern like the proposed makes total sense. We have definitely been frustrated by the fact that the state that changes the enabled-ness of an Action is not read at the same time that the apply()
closure of the action is invoked (and thus can introduce inconsistencies).
However, this use is not the only way that we use Action. In our codebase, approximately 2 of every 3 Actions use input values. As a specific example from our codebase, say that we have an Action that presents a modal when apply()
is invoked. The action has the following signature:
Action<Bool, ModalViewModel>
Where the input Bool
value is whether or not the presentation is animated, and the apply
SignalProducer
sends values of type ModalViewModel
for the view model backing the presentation. Additionally, this Action is enabled when the presentation can occur, and disabled for the entirety of the presentation (until the modal is dismissed).
In this case, if the input type was removed, there would be no way for a consumer (such as a button) to indicate whether or not the presentation would be animated. Additionally, the enabled-ness of this Action does not depend on its inputs, which (as far as I can tell) is incompatible with the proposed changes to action.
Following from the above, perhaps this behavior could be built in a way where it is available as an option when creating Actions, but is not the only way to work with them (somewhat like #22). Throwing out the ability for Action
to have an InputType
would (in my mind) be a much greater loss than the current frustration of having to deal with enabled-ness of Actions not necessarily being in sync with invocations of apply()
.
from reactiveswift.
Where the input Bool value is whether or not the presentation is animated
Could you share a bit more about where the Bool
value comes from?
from reactiveswift.
Sure—the Bool value can come from a number of places:
- The
UIViewController
that consumes the view model can decide to perform an animated presentation, and executes the Action withtrue
(in the case of a cell or button being tapped) - Within the view model itself, if a presentation is triggered as a side-effect of another action.
- A consumer of the view model that wants to perform a presentation non-animatedly (in the case of a deep link that synchronously creates a stack of presented modals at launch-time)
from reactiveswift.
So it seems clear that there's some value to both ideas: a) Action
having an Input
type, and b) Action
being able to observe some external state in a thread-safe way.
Earlier on in #22 I had an idea to make (b) happen, but never got it to work. This conversation inspired me to take another crack at it, and I think I've got it: fea3dc6.
@mdiep's original example can now work, with only a slight change to preserve the Input
parameter:
class LoginViewModel {
let username = Property<String>()
let password = Property<String>()
let action: Action<(), Void, LoginError> // input type is still part of the type
init() {
let form = Property.combineLatest(username, password)
// this is still a little messy, but I think we can
// layer some nicer convenience initialisers on top of this API
action = Action(_internalState: input, enabledIf: { !$0.isEmpty && !$1.isEmpty }) { (username, password), _ in
…
}
}
}
// elsewhere
loginViewModel.action.apply()
from reactiveswift.
That approach looks fine to me!
from reactiveswift.
Related Issues (20)
- Release 6.6.0 requires Swift 5.3 and therefore Xcode 12.x
- Using "<~" binding function with Signal.Observers causes memory leaks. HOT 3
- Using old xcconfig causing problems with Carthage & Apple Silicon macOS builds
- Support await / AsyncSequence HOT 13
- Playground does not work HOT 1
- Hello, ask a question about Disposable, thanks HOT 1
- What's means 'targeting' in QueueScheduler? HOT 2
- Signal.merge reported issue after migrating from ReactiveSwift from 4.0.0 -> 6.6.0 HOT 2
- xCode13 CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
- Usage with SwiftUI HOT 4
- Could not find module 'ReactiveSwift' for target 'arm64-apple-ios-simulator' HOT 2
- [BUG] XCode 14 beta 3 - bitcode issue HOT 1
- Is it planned to add count argument to `collect(every:on:skipEmpty:discardWhenCompleted:)`?
- Adopt OSAllocatedUnfairLock on iOS 16 HOT 9
- Add support for mapping Property/MutableProperty types to a Binding
- Signal triggered by phone call when device is closed HOT 3
- Infinite recursion in observeSwitchToLatest() HOT 3
- EXC_BAD_ACCESS Crashes occur in xcode15 HOT 4
- Reactive Swift crash with getCache function and observeSwitchToLatest signal
- Is there any plan for future releases? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from reactiveswift.