RxAutomaton
RxAutomaton copied to clipboard
Pass input parameters to producer in mappings
Along the lines of #1, I'd like to be able to pass the parameters from the input into the producer. For example, if I have a producer: discoverCharacteristicsProducer: ([CBService]) -> Observable<Event>
, I know I can call it by using a closure as my NextMapping
in the last element ^ I capture the services
from the .discoveredServices
event, and I then pass it into the discoverCharacteristicsProducer
Is it possible to do capture the param using the pipe mapping syntax? I'm sure theres some way to compose the operation, I'm not able to think of the type signature for a new |
operator that would do this
TL;DR: I'm missing an elegant way to pass an associated value
from Input
into the Producer
@AndrewSB Thanks for your suggestions along with #1! 😄
For #2, I was formerly implementing exact the same code in inamiy/ReactiveAutomaton@561b137 with the new type NextProducerGenerator
, but reverted afterward to avoid too much complexity.
My personal feeling is that it's not easy to keep using table-like syntax when many different types are involved in [NextMapping]
(before reduce
), especially when handling multiple input types as discussed in #1.
But this could be a challenging topic to implement it. Let me take some time to think 🤔
I think all thats needed are two new |
operators, that take. The first which captures the variable from the enum associated value, and the second that passes that captured value to the producer.
What do you think?
This is an example of one of my nextMappings:
var discoveringServicesToDiscoveringCharacteristics: Automaton<State, Event>.NextMapping { //swiftlint:disable:this variable_name
return { (state: State, input: Event) in
guard case .discoveredServices(let services) = input else { return nil }
guard [.discoveringServices, .waitingToDiscoverCharacteristics].contains(state) else { return nil }
return (.discoveringCharacteristics, self.discoverCharacteristicsProducer(services))
}
}
The significant parts is capturing the services from the .discoveredServices
input, and passing it to the discoverCharacteristicsProducer
I think (but Im not sure) that the change is simpler than what you were trying in https://github.com/inamiy/ReactiveAutomaton/commit/561b137, if we make it so this function becomes generic, on T?
, then if the enum has an associated value, T?
gets filled in, and gets passes into the NextMapping constructor
I'm not sure though. Does that make any sense to you?
I think I've come up with a partially complete solution. Very similar to your https://github.com/inamiy/ReactiveAutomaton/commit/561b137, but not as polished.
Basically, I defined a function on genericT
that can take an input enum that has one associatedValue, and a fromState to produce a Mapping:
func | <T, State, Input: Equatable & EnumAssociatedValueReadable>(input: @escaping (T) -> Input, transition: Transition<State>) -> (State, Input) -> State? {
return { fromState, event in
guard let eventAssociatedValue = event.associated.value as? T else {
return nil
}
// make sure that the event passed in matches the input param with the same associated value
if input(eventAssociatedValue) == event && transition.fromState(fromState) {
return transition.toState
} else {
return nil
}
}
}
It's a combination of the two functions (1 and 2) that work with a generic associatedValue.
It also uses an protocol I found in this stack overflow post called EnumAssociatedValueReadable
:
protocol EnumAssociatedValueReadable {
// implemented for one associated value, probably can be extended to support multiple
var associated: (label: String, value: Any?) { get }
}
extension EnumAssociatedValueReadable {
var associated: (label: String, value: Any?) {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return (associated.label!, associated.value)
}
return ("\(self)", nil)
}
}
}
and I can use it with the first example from my first comment in this issue (discovered(rssi: 0) | .none => .connecting | connectProducer
like this Event.discovered | .none => .connecting | connectProducer
, where the first pipe operator is the new one I defined above, and the second the same as before, defined in the Mapping+Helper: https://github.com/inamiy/RxAutomaton/blob/swift/3.0/Sources/Mapping%2BHelper.swift#L61
So ^ is the first new |
operator. I think you were right, and for the second, something like the NextProducerGenerator
has to be defined 😁
Sorry for late reply (I was on the trip, and still busy for another couple of weeks). Thanks for your idea, I'm still glancing your code though, it looks amazing!
However, I'm not positive with using reflection to extract the associated value. There should be some better way, and I'm thinking of lifting the type from:
typealias NextMapping = (State, Input) -> (State, Producer)
to:
typealias NextMapping2 = T -> (State, Input) -> T -> (State, Producer)
// or, typealias NextMapping2 = (State, T -> Input) -> (State, T -> Producer)
and add more helper functions to transform NextMapping
to NextMapping2
.
@inamiy are you against using reflection for the lack of clarity? Or because it seems like the wrong approach/hard result?
Your approach looks good too!