From 7606af80a0ce4e8ef824659afa912050b84065de Mon Sep 17 00:00:00 2001 From: SeungYeop Yeom Date: Sun, 14 Jan 2024 22:11:07 +0900 Subject: [PATCH] Support for result builder in `concat` and `merge` --- Sources/OneWay/AnyEffect.swift | 24 +- Sources/OneWay/EffectBuilder.swift | 15 -- Sources/OneWay/EffectsBuilder.swift | 52 ++++ Tests/OneWayTests/EffectsBuilderTests.swift | 263 ++++++++++++++++++++ 4 files changed, 335 insertions(+), 19 deletions(-) delete mode 100644 Sources/OneWay/EffectBuilder.swift create mode 100644 Sources/OneWay/EffectsBuilder.swift create mode 100644 Tests/OneWayTests/EffectsBuilderTests.swift diff --git a/Sources/OneWay/AnyEffect.swift b/Sources/OneWay/AnyEffect.swift index 6416af2..070152f 100644 --- a/Sources/OneWay/AnyEffect.swift +++ b/Sources/OneWay/AnyEffect.swift @@ -190,14 +190,22 @@ extension AnyEffect { ).eraseToAnyEffect() } + /// An effect that concatenates a list of effects together into a single effect, which runs the + /// effects one after the other. + /// + /// - Parameters: + /// - priority: The priority of the task. + /// Pass `nil` to use the priority from `Task.currentPriority`. + /// - build: A builder that makes effects. + /// - Returns: A new effect. @inlinable public static func concat( priority: TaskPriority? = nil, - @EffectsBuilder _ effects: () -> [AnyEffect] + @EffectsBuilder _ build: () -> [AnyEffect] ) -> AnyEffect { Effects.Concat( priority: priority, - effects() + build() ).eraseToAnyEffect() } @@ -220,14 +228,22 @@ extension AnyEffect { ).eraseToAnyEffect() } + /// An effect that merges a list of effects together into a single effect, which runs the + /// effects at the same time. + /// + /// - Parameters: + /// - priority: The priority of the task. + /// Pass `nil` to use the priority from `Task.currentPriority`. + /// - build: A builder that makes effects. + /// - Returns: A new effect. @inlinable public static func merge( priority: TaskPriority? = nil, - @EffectsBuilder _ effects: () -> [AnyEffect] + @EffectsBuilder _ build: () -> [AnyEffect] ) -> AnyEffect { Effects.Merge( priority: priority, - effects() + build() ).eraseToAnyEffect() } } diff --git a/Sources/OneWay/EffectBuilder.swift b/Sources/OneWay/EffectBuilder.swift deleted file mode 100644 index 39de2cc..0000000 --- a/Sources/OneWay/EffectBuilder.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// OneWay -// The MIT License (MIT) -// -// Copyright (c) 2022-2023 SeungYeop Yeom ( https://github.com/DevYeom ). -// - -import Foundation - -@resultBuilder -public struct EffectsBuilder { - public static func buildBlock(_ effects: AnyEffect...) -> [AnyEffect] { - return effects - } -} diff --git a/Sources/OneWay/EffectsBuilder.swift b/Sources/OneWay/EffectsBuilder.swift new file mode 100644 index 0000000..a7850cd --- /dev/null +++ b/Sources/OneWay/EffectsBuilder.swift @@ -0,0 +1,52 @@ +// +// OneWay +// The MIT License (MIT) +// +// Copyright (c) 2022-2023 SeungYeop Yeom ( https://github.com/DevYeom ). +// + +import Foundation + +@resultBuilder +public enum EffectsBuilder { + public static func buildArray(_ components: [[AnyEffect]]) -> [AnyEffect] { + components.flatMap { $0 } + } + + public static func buildBlock() -> [AnyEffect] { + [] + } + + public static func buildBlock(_ effects: AnyEffect...) -> [AnyEffect] { + effects + } + + public static func buildBlock(_ components: [AnyEffect]...) -> [AnyEffect] { + components.flatMap { $0 } + } + + public static func buildEither(first component: [AnyEffect]) -> [AnyEffect] { + component + } + + public static func buildEither(second component: [AnyEffect]) -> [AnyEffect] { + component + } + + public static func buildExpression(_ expression: AnyEffect) -> [AnyEffect] { + [expression] + } + + public static func buildFinalResult(_ component: [AnyEffect]) -> [AnyEffect] { + component + } + + public static func buildLimitedAvailability(_ component: [AnyEffect]) -> [AnyEffect] { + component + } + + public static func buildOptional(_ component: [AnyEffect]?) -> [AnyEffect] { + guard let component = component else { return [] } + return component + } +} diff --git a/Tests/OneWayTests/EffectsBuilderTests.swift b/Tests/OneWayTests/EffectsBuilderTests.swift new file mode 100644 index 0000000..430e121 --- /dev/null +++ b/Tests/OneWayTests/EffectsBuilderTests.swift @@ -0,0 +1,263 @@ +// +// OneWay +// The MIT License (MIT) +// +// Copyright (c) 2022-2023 SeungYeop Yeom ( https://github.com/DevYeom ). +// + +import OneWay +import XCTest + +final class EffectsBuilderTests: XCTestCase { + func test_array() async { + do { + let effect = AnyEffect.concat { + let effects = [ + AnyEffect.just(1), + AnyEffect.just(2), + AnyEffect.just(3), + ] + for effect in effects { + effect + } + } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, [1, 2, 3]) + } + + do { + let effect = AnyEffect.merge { + let effects = [ + AnyEffect.just(1), + AnyEffect.just(2), + AnyEffect.just(3), + ] + for effect in effects { + effect + } + } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, [1, 2, 3]) + } + } + + func test_emptyBlock() async { + do { + let effect = AnyEffect.concat { } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, []) + } + + do { + let effect = AnyEffect.merge { } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, []) + } + } + + func test_block() async { + do { + let effect = AnyEffect.concat { + AnyEffect.just(1) + AnyEffect.just(2) + AnyEffect.just(3) + } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, [1, 2, 3]) + } + + do { + let effect = AnyEffect.merge { + AnyEffect.just(1) + AnyEffect.just(2) + AnyEffect.just(3) + } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, [1, 2, 3]) + } + } + + func test_conditionalBlock() async { + enum Order { + case first + case second + } + let trueCondition = true + let falseCondition = false + let order = Order.second + + do { + let effect = AnyEffect.concat { + AnyEffect.just(1) + + if trueCondition { + AnyEffect.just(2) + } + if falseCondition { + AnyEffect.just(3) + } else { + AnyEffect.just(4) + } + + switch order { + case .first: + AnyEffect.just(5) + case .second: + AnyEffect.just(6) + } + } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, [1, 2, 4, 6]) + } + + do { + let effect = AnyEffect.merge { + AnyEffect.just(1) + + if trueCondition { + AnyEffect.just(2) + } + if falseCondition { + AnyEffect.just(3) + } else { + AnyEffect.just(4) + } + + switch order { + case .first: + AnyEffect.just(5) + case .second: + AnyEffect.just(6) + } + } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, [1, 2, 4, 6]) + } + } + + func test_optionalBlock() async { + let someValue: AnyEffect? = .just(1) + let someValue2: AnyEffect? = .just(2) + let noneValue: AnyEffect? = nil + + do { + let effect = AnyEffect.concat { + if let someValue { + someValue + } + if case let .some(value) = someValue2 { + value + } + if let noneValue { + noneValue + } + } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, [1, 2]) + } + + do { + let effect = AnyEffect.merge { + if let someValue { + someValue + } + if case let .some(value) = someValue2 { + value + } + if let noneValue { + noneValue + } + } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, [1, 2]) + } + } + + func test_limitedAvailabilityBlock() async { + do { + let effect = AnyEffect.concat { + AnyEffect.just(1) + if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + AnyEffect.just(2) + } else { + AnyEffect.just(3) + } + } + + var result: [Int] = [] + for await value in effect.values { + result.append(value) + } + + XCTAssertEqual(result, [1, 2]) + } + + do { + let effect = AnyEffect.merge { + AnyEffect.just(1) + if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + AnyEffect.just(2) + } else { + AnyEffect.just(3) + } + } + + var result: Set = [] + for await value in effect.values { + result.insert(value) + } + + XCTAssertEqual(result, [1, 2]) + } + } +}