Skip to content
This repository has been archived by the owner on Dec 12, 2021. It is now read-only.

Commit

Permalink
Rename NextMapping to EffectMapping
Browse files Browse the repository at this point in the history
  • Loading branch information
inamiy committed Apr 16, 2017
1 parent 68910e0 commit cad12ff
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class AutomatonViewController: UIViewController
let canForceLogout: (State) -> Bool = [.loggingIn, .loggedIn].contains

/// Transition mapping.
let mappings: [Automaton<State, Input>.NextMapping] = [
let mappings: [Automaton<State, Input>.EffectMapping] = [

/* Input | fromState => toState | Effect */
/* ----------------------------------------------------------*/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let forcelogoutOKProducer = /* do something more special, ..., and send `Input.l
let canForceLogout: State -> Bool = [.loggingIn, .loggedIn].contains

// 2. Setup state-transition mappings.
let mappings: [Automaton<State, Input>.NextMapping] = [
let mappings: [Automaton<State, Input>.EffectMapping] = [

/* Input | fromState => toState | Effect */
/* ----------------------------------------------------------*/
Expand Down
16 changes: 8 additions & 8 deletions RxAutomaton.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
4822CC421D6197A800783A77 /* ToRACHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC411D6197A800783A77 /* ToRACHelper.swift */; };
487BDE631D619D3200C86902 /* AnyMappingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC2F1D6194FD00783A77 /* AnyMappingSpec.swift */; };
487BDE641D619D4500C86902 /* TerminatingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC331D6194FD00783A77 /* TerminatingSpec.swift */; };
487BDE6B1D61AFF300C86902 /* StrategyLatestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC321D6194FD00783A77 /* StrategyLatestSpec.swift */; };
487BDE6C1D61B03700C86902 /* NextMappingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC311D6194FD00783A77 /* NextMappingSpec.swift */; };
487BDE6B1D61AFF300C86902 /* EffectMappingLatestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC321D6194FD00783A77 /* EffectMappingLatestSpec.swift */; };
487BDE6C1D61B03700C86902 /* EffectMappingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822CC311D6194FD00783A77 /* EffectMappingSpec.swift */; };
488738E01D61689000BF70F4 /* RxAutomaton.h in Headers */ = {isa = PBXBuildFile; fileRef = 488738DF1D61689000BF70F4 /* RxAutomaton.h */; settings = {ATTRIBUTES = (Public, ); }; };
488738E71D61689100BF70F4 /* RxAutomaton.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 488738DC1D61689000BF70F4 /* RxAutomaton.framework */; };
488738F71D6168A600BF70F4 /* Automaton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488738F61D6168A600BF70F4 /* Automaton.swift */; };
Expand Down Expand Up @@ -83,8 +83,8 @@
1FCAB4E51DC794A900EA6EBF /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4822CC2F1D6194FD00783A77 /* AnyMappingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyMappingSpec.swift; sourceTree = "<group>"; };
4822CC301D6194FD00783A77 /* MappingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappingSpec.swift; sourceTree = "<group>"; };
4822CC311D6194FD00783A77 /* NextMappingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NextMappingSpec.swift; sourceTree = "<group>"; };
4822CC321D6194FD00783A77 /* StrategyLatestSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrategyLatestSpec.swift; sourceTree = "<group>"; };
4822CC311D6194FD00783A77 /* EffectMappingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectMappingSpec.swift; sourceTree = "<group>"; };
4822CC321D6194FD00783A77 /* EffectMappingLatestSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectMappingLatestSpec.swift; sourceTree = "<group>"; };
4822CC331D6194FD00783A77 /* TerminatingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TerminatingSpec.swift; sourceTree = "<group>"; };
4822CC391D61961300783A77 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4822CC3F1D61969C00783A77 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -212,10 +212,10 @@
children = (
4822CC3E1D61969C00783A77 /* Fixtures */,
4822CC301D6194FD00783A77 /* MappingSpec.swift */,
4822CC311D6194FD00783A77 /* NextMappingSpec.swift */,
4822CC311D6194FD00783A77 /* EffectMappingSpec.swift */,
4822CC2F1D6194FD00783A77 /* AnyMappingSpec.swift */,
1FA0AC451DE8AC2B007F01E0 /* StateFuncMappingSpec.swift */,
4822CC321D6194FD00783A77 /* StrategyLatestSpec.swift */,
4822CC321D6194FD00783A77 /* EffectMappingLatestSpec.swift */,
4822CC331D6194FD00783A77 /* TerminatingSpec.swift */,
488738ED1D61689100BF70F4 /* Info.plist */,
);
Expand Down Expand Up @@ -454,9 +454,9 @@
1FA0AC461DE8AC2B007F01E0 /* StateFuncMappingSpec.swift in Sources */,
4822CC401D61969C00783A77 /* Fixtures.swift in Sources */,
487BDE631D619D3200C86902 /* AnyMappingSpec.swift in Sources */,
487BDE6C1D61B03700C86902 /* NextMappingSpec.swift in Sources */,
487BDE6C1D61B03700C86902 /* EffectMappingSpec.swift in Sources */,
487BDE641D619D4500C86902 /* TerminatingSpec.swift in Sources */,
487BDE6B1D61AFF300C86902 /* StrategyLatestSpec.swift in Sources */,
487BDE6B1D61AFF300C86902 /* EffectMappingLatestSpec.swift in Sources */,
4822CC421D6197A800783A77 /* ToRACHelper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
39 changes: 20 additions & 19 deletions Sources/Automaton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ public final class Automaton<State, Input>
/// Basic state-transition function type.
public typealias Mapping = (State, Input) -> State?

/// Transducer (input & output) mapping with `Observable<Input>` (next-producer) as output,
/// which **wraps additional effects and emit next input values**
/// for automatic & continuous state-transitions.
public typealias NextMapping = (State, Input) -> (State, Observable<Input>)?
/// Transducer (input & output) mapping with
/// `Observable<Input>` (additional effect) as output,
/// which may emit next input values for continuous state-transitions.
public typealias EffectMapping = (State, Input) -> (State, Observable<Input>)?

/// `Reply` signal that notifies either `.success` or `.failure` of state-transition on every input.
public let replies: Observable<Reply<State, Input>>

/// Current state.
public let state: Variable<State> // TODO: AnyProperty
/// - Todo: Use RxProperty https://github.com/inamiy/RxProperty
public let state: Variable<State>

private let _replyObserver: AnyObserver<Reply<State, Input>>

Expand All @@ -43,23 +44,23 @@ public final class Automaton<State, Input>
/// - Parameters:
/// - state: Initial state.
/// - input: `Observable<Input>` that automaton receives.
/// - mapping: Simple `Mapping` that designates next state only (no next-producer).
/// - mapping: Simple `Mapping` that designates next state only (no additional effect).
///
public convenience init(state initialState: State, input inputSignal: Observable<Input>, mapping: @escaping Mapping)
{
self.init(state: initialState, input: inputSignal, mapping: _compose(_toNextMapping, mapping))
self.init(state: initialState, input: inputSignal, mapping: _compose(_toEffectMapping, mapping))
}

///
/// Initializer using `NextMapping`.
/// Initializer using `EffectMapping`.
///
/// - Parameters:
/// - state: Initial state.
/// - input: `Observable<Input>` that automaton receives.
/// - mapping: `NextMapping` that designates next state and also generates next-producer.
/// - strategy: `FlattenStrategy` that flattens next-producer generated by `NextMapping`.
/// - mapping: `EffectMapping` that designates next state and also generates additional effect.
/// - strategy: `FlattenStrategy` that flattens additional effect generated by `EffectMapping`.
///
public init(state initialState: State, input inputSignal: Observable<Input>, mapping: @escaping NextMapping, strategy: FlattenStrategy = .merge)
public init(state initialState: State, input inputSignal: Observable<Input>, mapping: @escaping EffectMapping, strategy: FlattenStrategy = .merge)
{
let stateProperty = Variable(initialState)
self.state = stateProperty // TODO: AnyProperty(stateProperty)
Expand All @@ -68,7 +69,7 @@ public final class Automaton<State, Input>
(self.replies, self._replyObserver) = (p.asObservable(), AnyObserver(eventHandler: p.asObserver().on))

/// Recursive input-producer that sends inputs from `inputSignal`
/// and also next-producers generated by `NextMapping`.
/// and also additional effects generated by `EffectMapping`.
func recurInputProducer(_ inputProducer: Observable<Input>, strategy: FlattenStrategy) -> Observable<Input>
{
return Observable<Input>.create { observer in
Expand All @@ -80,17 +81,17 @@ public final class Automaton<State, Input>
.shareReplay(1)

let successSignal = mappingSignal
.filterMap { input, fromState, nextProducer in
return nextProducer.map { (input, fromState, $0) }
.filterMap { input, fromState, effect in
return effect.map { (input, fromState, $0) }
}
.flatMap(strategy) { input, fromState, nextProducer -> Observable<Input> in
return recurInputProducer(nextProducer, strategy: strategy)
.flatMap(strategy) { input, fromState, effect -> Observable<Input> in
return recurInputProducer(effect, strategy: strategy)
.startWith(input)
}

let failureSignal = mappingSignal
.filterMap { input, fromState, nextProducer -> Input? in
return nextProducer == nil ? input : nil
.filterMap { input, fromState, effect -> Input? in
return effect == nil ? input : nil
}

let mergedProducer = Observable.of(failureSignal, successSignal).merge()
Expand Down Expand Up @@ -141,7 +142,7 @@ private func _compose<A, B, C>(_ g: @escaping (B) -> C, _ f: @escaping (A) -> B)
return { x in g(f(x)) }
}

private func _toNextMapping<State, Input>(toState: State?) -> (State, Observable<Input>)?
private func _toEffectMapping<State, Input>(toState: State?) -> (State, Observable<Input>)?
{
if let toState = toState {
return (toState, .empty())
Expand Down
10 changes: 5 additions & 5 deletions Sources/Mapping+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ public func | <State, Input: Equatable>(input: Input, transition: @escaping (Sta
return { $0 == input } | transition
}

// MARK: `|` (Automaton.NextMapping constructor)
// MARK: `|` (Automaton.EffectMapping constructor)

public func | <State, Input>(mapping: @escaping Automaton<State, Input>.Mapping, nextInputProducer: Observable<Input>) -> Automaton<State, Input>.NextMapping
public func | <State, Input>(mapping: @escaping Automaton<State, Input>.Mapping, nextInputProducer: Observable<Input>) -> Automaton<State, Input>.EffectMapping
{
return { fromState, input in
if let toState = mapping(fromState, input) {
Expand Down Expand Up @@ -111,9 +111,9 @@ public func reduce<State, Input, Mappings: Sequence>(_ mappings: Mappings) -> Au
}
}

/// Folds multiple `Automaton.NextMapping`s into one (preceding mapping has higher priority).
public func reduce<State, Input, Mappings: Sequence>(_ mappings: Mappings) -> Automaton<State, Input>.NextMapping
where Mappings.Iterator.Element == Automaton<State, Input>.NextMapping
/// Folds multiple `Automaton.EffectMapping`s into one (preceding mapping has higher priority).
public func reduce<State, Input, Mappings: Sequence>(_ mappings: Mappings) -> Automaton<State, Input>.EffectMapping
where Mappings.Iterator.Element == Automaton<State, Input>.EffectMapping
{
return { fromState, input in
for mapping in mappings {
Expand Down
4 changes: 2 additions & 2 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import Quick

Quick.QCKMain([
MappingSpec.self,
NextMappingSpec.self,
EffectMappingSpec.self,
AnyMappingSpec.self,
StateFuncMappingSpec.self,
NextMappingLatestSpec.self,
EffectMappingLatestSpec.self,
TerminatingSpec.self
])
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import RxAutomaton
import Quick
import Nimble

/// NextMapping tests with `strategy = .Latest`.
class NextMappingLatestSpec: QuickSpec
/// EffectMapping tests with `strategy = .latest`.
class EffectMappingLatestSpec: QuickSpec
{
override func spec()
{
typealias Automaton = RxAutomaton.Automaton<AuthState, AuthInput>
typealias NextMapping = Automaton.NextMapping
typealias EffectMapping = Automaton.EffectMapping

let (signal, observer) = Observable<AuthInput>.pipe()
var automaton: Automaton?
var lastReply: Reply<AuthState, AuthInput>?

describe("strategy = `.Latest`") {
describe("strategy = `.latest`") {

var testScheduler: TestScheduler!

Expand All @@ -40,7 +40,7 @@ class NextMappingLatestSpec: QuickSpec
Observable.just(AuthInput.logoutOK)
.delay(1, onScheduler: testScheduler)

let mappings: [Automaton.NextMapping] = [
let mappings: [Automaton.EffectMapping] = [
.login | .loggedOut => .loggingIn | loginOKProducer,
.loginOK | .loggingIn => .loggedIn | .empty(),
.logout | .loggedIn => .loggingOut | logoutOKProducer,
Expand All @@ -57,7 +57,7 @@ class NextMappingLatestSpec: QuickSpec
lastReply = nil
}

it("`strategy = .Latest` should not interrupt inner next-producers when transition fails") {
it("`strategy = .latest` should not interrupt inner effects when transition fails") {
expect(automaton?.state.value) == .loggedOut
expect(lastReply).to(beNil())

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// NextMappingSpec.swift
// EffectMappingSpec.swift
// RxAutomaton
//
// Created by Yasuhiro Inami on 2016-08-15.
Expand All @@ -14,19 +14,19 @@ import Nimble

/// Tests for `(State, Input) -> (State, Output)?` mapping
/// where `Output = Observable<Input>`.
class NextMappingSpec: QuickSpec
class EffectMappingSpec: QuickSpec
{
override func spec()
{
typealias Automaton = RxAutomaton.Automaton<AuthState, AuthInput>
typealias NextMapping = Automaton.NextMapping
typealias EffectMapping = Automaton.EffectMapping

let (signal, observer) = Observable<AuthInput>.pipe()
var automaton: Automaton?
var lastReply: Reply<AuthState, AuthInput>?
var testScheduler: TestScheduler!

describe("Syntax-sugar NextMapping") {
describe("Syntax-sugar EffectMapping") {

beforeEach {
testScheduler = TestScheduler()
Expand All @@ -41,7 +41,7 @@ class NextMappingSpec: QuickSpec
Observable.just(AuthInput.logoutOK)
.delay(1, onScheduler: testScheduler)

let mappings: [Automaton.NextMapping] = [
let mappings: [Automaton.EffectMapping] = [
.login | .loggedOut => .loggingIn | loginOKProducer,
.loginOK | .loggingIn => .loggedIn | .empty(),
.logout | .loggedIn => .loggingOut | logoutOKProducer,
Expand Down Expand Up @@ -95,7 +95,7 @@ class NextMappingSpec: QuickSpec

}

describe("Func-based NextMapping") {
describe("Func-based EffectMapping") {

beforeEach {
testScheduler = TestScheduler()
Expand All @@ -110,7 +110,7 @@ class NextMappingSpec: QuickSpec
Observable.just(AuthInput.logoutOK)
.delay(1, onScheduler: testScheduler)

let mapping: NextMapping = { fromState, input in
let mapping: EffectMapping = { fromState, input in
switch (fromState, input) {
case (.loggedOut, .login):
return (.loggingIn, loginOKProducer)
Expand Down Expand Up @@ -173,7 +173,7 @@ class NextMappingSpec: QuickSpec
}

/// https://github.com/inamiy/RxAutomaton/issues/3
describe("Next-producer should be called only once per input") {
describe("Additional effect should be called only once per input") {

var effectCallCount = 0

Expand All @@ -192,12 +192,12 @@ class NextMappingSpec: QuickSpec
})
}

let mappings: [Automaton.NextMapping] = [
let mappings: [Automaton.EffectMapping] = [
.login | .loggedOut => .loggingIn | loginOKProducer,
.loginOK | .loggingIn => .loggedIn | .empty(),
]

// strategy = `.Merge`
// strategy = `.merge`
automaton = Automaton(state: .loggedOut, input: signal, mapping: reduce(mappings), strategy: .merge)

_ = automaton?.replies.observeValues { reply in
Expand Down
4 changes: 2 additions & 2 deletions Tests/RxAutomatonTests/StateFuncMappingSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ class StateFuncMappingSpec: QuickSpec
describe("State-change function mapping") {

typealias Automaton = RxAutomaton.Automaton<CountState, CountInput>
typealias NextMapping = Automaton.NextMapping
typealias EffectMapping = Automaton.EffectMapping

let (signal, observer) = Observable<CountInput>.pipe()
var automaton: Automaton?

beforeEach {
let mappings: [Automaton.NextMapping] = [
let mappings: [Automaton.EffectMapping] = [
.increment | { $0 + 1 } | .empty(),
.decrement | { $0 - 1 } | .empty(),
]
Expand Down
Loading

0 comments on commit cad12ff

Please sign in to comment.