Skip to content

Commit

Permalink
Release/1.0.0 (#11)
Browse files Browse the repository at this point in the history
* Add KeyPathActor and update tests

* Add doc strings

* Add asyncForEach and tests

* Update README

* Remove default parameter value
  • Loading branch information
0xLeif authored Aug 18, 2022
1 parent b22626f commit ea62ba5
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 21 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,33 @@ let forkedActor = ForkedActor(
}
)

try await forkedActor.act()
let actorValue = await forkedActor.act().value

let actorValue = await forkedActor.actor.value
XCTAssertEqual(actorValue, 3)
```

### ForkedActor KeyPathActor<Int> Example

```swift
let forkedActor = ForkedActor(
value: 0,
leftOutput: { actor in
await actor.update(to: { $0 + 1 })
},
rightOutput: { actor in
try await actor.fork(
leftOutput: { actor in
await actor.update(to: { $0 + 1 })
},
rightOutput: { actor in
await actor.update(\.self, to: { $0 + 1 })
}
)
.act()
}
)

let actorValue = try await forkedActor.act().value

XCTAssertEqual(actorValue, 3)
```
Expand Down
31 changes: 31 additions & 0 deletions Sources/Fork/Actors/KeyPathActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// A generic Actor that uses KeyPaths to update and set values
public actor KeyPathActor<Value> {

/// The wrapped value of the Actor
public var value: Value

/// Wrapped the Value into a KeyPathActor
public init(value: Value) { self.value = value }

/// Set the value
public func set(
to newValue: Value
) { value = newValue }

/// Set the key path to a new value
public func set<KeyPathValue>(
_ keyPath: WritableKeyPath<Value, KeyPathValue>,
to newValue: KeyPathValue
) { value[keyPath: keyPath] = newValue }

/// Update the value
public func update(
to newValue: (Value) -> Value
) { set(to: newValue(value)) }

/// Update the key path to a new value
public func update<KeyPathValue>(
_ keyPath: WritableKeyPath<Value, KeyPathValue>,
to newValue: (KeyPathValue) -> KeyPathValue
) { set(keyPath, to: newValue(value[keyPath: keyPath])) }
}
23 changes: 22 additions & 1 deletion Sources/Fork/Extensions/Array+ForkedArray.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extension Array {
/// Create a ``ForkedArray`` from the current `Array`
public func fork<Output>(
filter: @escaping (Element) async throws -> Bool = { _ in true },
filter: @escaping (Element) async throws -> Bool,
map: @escaping (Element) async throws -> Output
) -> ForkedArray<Element, Output> {
ForkedArray(
Expand All @@ -11,6 +11,13 @@ extension Array {
)
}

/// Create a ``ForkedArray`` from the current `Array`
public func fork<Output>(
map: @escaping (Element) async throws -> Output
) -> ForkedArray<Element, Output> {
fork(filter: { _ in true}, map: map)
}

/// Create a ``ForkedArray`` from the current `Array` and get the Output Array
public func forked<Output>(
filter: @escaping (Element) async throws -> Bool,
Expand All @@ -24,6 +31,13 @@ extension Array {
.output()
}

/// Create a ``ForkedArray`` from the current `Array` and get the Output Array
public func forked<Output>(
map: @escaping (Element) async throws -> Output
) async throws -> [Output] {
try await forked(filter: { _ in true }, map: map)
}

/// Returns an array containing the results of mapping the given closure over the sequence’s elements.
public func asyncMap<Output>(
_ transform: @escaping (Element) async throws -> Output
Expand All @@ -37,4 +51,11 @@ extension Array {
) async throws -> [Element] {
try await fork(filter: isIncluded, map: identity).output()
}

/// Calls the given closure for each of the elements in the Array. This function uses ``ForkedArray`` and will be parallelized when possible.
public func asyncForEach(
_ transform: @escaping (Element) async throws -> Void
) async throws {
_ = try await asyncMap(transform)
}
}
31 changes: 30 additions & 1 deletion Sources/Fork/ForkedActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,35 @@ public struct ForkedActor<Value: Actor> {
)
}

/// Create a ``ForkedActor`` using a single value that is passed into the left and right async functions.
/// - Parameters:
/// - value: Any value to be passed into the map functions. This value is wrapped into an `actor` using ``KeyPathActor``.
/// - leftOutput: An `async` closure that uses the `actor` as its input
/// - rightOutput: An `async` closure that uses the `actor` as its input
public init<Input>(
value: Input,
leftOutput: @escaping (_ actor: Value) async throws -> Void,
rightOutput: @escaping (_ actor: Value) async throws -> Void
) where Value == KeyPathActor<Input> {
self.actor = KeyPathActor(value: value)
self.fork = Fork(
value: actor,
leftOutput: { actor in
try await leftOutput(actor)

return actor
},
rightOutput: { actor in
try await rightOutput(actor)

return actor
}
)
}

/// Asynchronously resolve the fork using the actor
public func act() async throws {
@discardableResult
public func act() async throws -> Value {
try Task.checkCancellation()

async let leftForkedTask = fork.left()
Expand All @@ -45,5 +72,7 @@ public struct ForkedActor<Value: Actor> {
_ = try await [leftForkedTask, rightForkedTask]

try Task.checkCancellation()

return actor
}
}
13 changes: 12 additions & 1 deletion Sources/Fork/ForkedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public struct ForkedArray<Value, Output> {
/// - output: An `async` closure that uses the `Array.Element` as its input
public init(
_ array: [Value],
filter: @escaping (Value) async throws -> Bool = { _ in true },
filter: @escaping (Value) async throws -> Bool,
map: @escaping (Value) async throws -> Output
) {
self.array = array
Expand All @@ -45,6 +45,17 @@ public struct ForkedArray<Value, Output> {
}
}

/// Create a ``ForkedArray`` using a single `Array`
/// - Parameters:
/// - array: The `Array` to be used in creating the output
/// - output: An `async` closure that uses the `Array.Element` as its input
public init(
_ array: [Value],
map: @escaping (Value) async throws -> Output
) {
self.init(array, filter: { _ in true }, map: map)
}

/// Asynchronously resolve the forked array
public func output() async throws -> [Output] {
try await fork.merged(
Expand Down
43 changes: 43 additions & 0 deletions Tests/ForkTests/ForkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,47 @@ final class ForkTests: XCTestCase {

XCTAssertEqual(output, "1010")
}

func testForkClosure() async throws {
let fork = Fork(
value: UUID.init,
leftOutput: { $0.uuidString == "UUID" },
rightOutput: { Array($0.uuidString.reversed().map(\.description)) }
)

let leftOutput = try await fork.left()
let rightOutput = try await fork.right()

XCTAssertEqual(leftOutput, false)
XCTAssertEqual(rightOutput.count, 36)

let mergedFork: () async throws -> [String] = fork.merge(
using: { bool, string in
if bool {
return string + string
}

return string
}
)

let output = try await mergedFork()

XCTAssertNotEqual(output, rightOutput)
}

func testForkVoid() async throws {
try await Fork(
leftOutput: { print("Hello", terminator: "") },
rightOutput: {
try await Fork(
leftOutput: { print(" ", terminator: "") },
rightOutput: { print("World", terminator: "") }
)
.merged()
}
)
.merged()
print()
}
}
24 changes: 9 additions & 15 deletions Tests/ForkTests/ForkedActorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,25 @@ class ForkedActorTests: XCTestCase {
}

func testHigherOrderForkedActor() async throws {
actor TestActor {
var value: Int = 0

func increment() {
value += 1
}
}

let forkedActor = ForkedActor(
actor: TestActor(),
value: 0,
leftOutput: { actor in
await actor.increment()
await actor.update(to: { $0 + 1 })
},
rightOutput: { actor in
try await actor.fork(
leftOutput: { await $0.increment() },
rightOutput: { await $0.increment() }
leftOutput: { actor in
await actor.update(to: { $0 + 1 })
},
rightOutput: { actor in
await actor.update(\.self, to: { $0 + 1 })
}
)
.act()
}
)

try await forkedActor.act()

let actorValue = await forkedActor.actor.value
let actorValue = try await forkedActor.act().value

XCTAssertEqual(actorValue, 3)
}
Expand Down
29 changes: 28 additions & 1 deletion Tests/ForkTests/ForkedArrayTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ import XCTest
@testable import Fork

class ForkedArrayTests: XCTestCase {
func testForkedArray() async throws {
let photoNames: [String] = (0 ... Int.random(in: 1 ..< 10)).map(\.description)
@Sendable func downloadPhoto(named: String) async -> String { named }
func show(_ photos: [String]) { }

let forkedArray = ForkedArray(
photoNames,
map: downloadPhoto(named:)
)
let photos = try await forkedArray.output()

XCTAssertEqual(photos, photoNames)
}

func testForkedArray_ForEach() async throws {
try await ["Hello", " ", "World", "!"].asyncForEach { print($0) }
}

func testForkedArray_none() async throws {
let photoNames: [String] = []
@Sendable func downloadPhoto(named: String) async -> String { named }
Expand All @@ -27,7 +45,6 @@ class ForkedArrayTests: XCTestCase {
@Sendable func downloadPhoto(named: String) async -> String { named }

let photos = try await photoNames.forked(
filter: { _ in true },
map: downloadPhoto(named:)
)

Expand All @@ -52,4 +69,14 @@ class ForkedArrayTests: XCTestCase {

XCTAssertEqual(photos, photoNames)
}

func testForkedArray_order() async throws {
let photoNames = ["Hello", " ", "World", "!"]
@Sendable func downloadPhoto(named: String) async -> String { named }

let forkedArray = photoNames.fork(map: downloadPhoto(named:))
let photos = try await forkedArray.output()

XCTAssertEqual(photos, photoNames)
}
}

0 comments on commit ea62ba5

Please sign in to comment.