{"id":18060650,"url":"https://github.com/0xleif/fork","last_synced_at":"2025-04-11T12:23:10.773Z","repository":{"id":50278445,"uuid":"518657586","full_name":"0xLeif/Fork","owner":"0xLeif","description":"🍴 Parallelize two or more async functions","archived":false,"fork":false,"pushed_at":"2024-09-27T18:06:23.000Z","size":66,"stargazers_count":10,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T02:16:36.159Z","etag":null,"topics":["actors","async","async-await","await","concurrency","fork","parallel","swift"],"latest_commit_sha":null,"homepage":"https://0xleif.github.io/Fork/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0xLeif.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-28T01:11:47.000Z","updated_at":"2024-10-21T15:03:12.000Z","dependencies_parsed_at":"2024-06-19T08:11:18.399Z","dependency_job_id":null,"html_url":"https://github.com/0xLeif/Fork","commit_stats":{"total_commits":45,"total_committers":1,"mean_commits":45.0,"dds":0.0,"last_synced_commit":"faa0f4c58c121a5d2a331011c1a1207319fe46cf"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xLeif%2FFork","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xLeif%2FFork/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xLeif%2FFork/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xLeif%2FFork/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xLeif","download_url":"https://codeload.github.com/0xLeif/Fork/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248401313,"owners_count":21097327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actors","async","async-await","await","concurrency","fork","parallel","swift"],"created_at":"2024-10-31T04:10:04.325Z","updated_at":"2025-04-11T12:23:10.752Z","avatar_url":"https://github.com/0xLeif.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fork\n\n*Parallelize two or more async functions*\n\n## What is Fork?\n\nFork is a Swift 6 library that allows for parallelizing multiple async functions. It provides a Fork struct that takes a single input and splits it into two separate async functions that return different outputs. The two functions can then be merged into one which returns a single output.\n\n## Why use Fork?\n\nAsynchronous programming in Swift can be made easier with the async-await syntax, but it can still be challenging to parallelize multiple functions. Fork simplifies this by allowing developers to create parallel tasks with ease.\n\nThe [Swift Book](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID641) has the following example for downloading multiple images.\n\n```swift\nlet firstPhoto = await downloadPhoto(named: photoNames[0])\nlet secondPhoto = await downloadPhoto(named: photoNames[1])\nlet thirdPhoto = await downloadPhoto(named: photoNames[2])\n\nlet photos = [firstPhoto, secondPhoto, thirdPhoto]\nshow(photos)\n```\n\nNow the code above is still asynchronous, but will only run one function at a time. In the example above, `firstPhoto` will be set first, then `secondPhoto`, and finally `thirdPhoto`.\n\nTo run these three async functions in parallel we need to change the code to this following example.\n\n```swift\nasync let firstPhoto = downloadPhoto(named: photoNames[0])\nasync let secondPhoto = downloadPhoto(named: photoNames[1])\nasync let thirdPhoto = downloadPhoto(named: photoNames[2])\n\nlet photos = await [firstPhoto, secondPhoto, thirdPhoto]\nshow(photos)\n```\n\nThe above code will now download all three photos at the same time. When all the photos have been downloaded it will show the photos.\n\nNow, using Fork we could simiplfy this to just a couple of lines!\n\n```swift\nlet photos = try await photoNames.asyncMap(downloadPhoto(named:))\nshow(photos)\n```\n\nWhen using Fork, functions will be ran in parallel and higher order forks will also be ran in parallel.\n\n## Objects \n- `Fork`: Using a single input create two separate async functions that return `LeftOutput` and `RightOutput`.\n- `ForkedArray`: Using a single array and a single async function, parallelize the work for each value of the array.\n- `BatchedForkedArray`: Using a single array and a single async function, batch the parallelized work for each value of the array\n- `ForkedActor`: Using a single actor create two separate async functions that are passed the actor.\n    - `KeyPathActor`: A generic Actor that uses KeyPaths to update and set values.\n\n## Basic usage\n\n```swift\nimport Fork\n```\n\n## Fork Example\n\n```swift\nlet fork = Fork(\n    value: 10,\n    leftOutput: { $0.isMultiple(of: 2) },\n    rightOutput: { \"\\($0)\" }\n)\n        \nlet leftOutput = try await fork.left()\nlet rightOutput = try await fork.right()\n\nXCTAssertEqual(leftOutput, true)\nXCTAssertEqual(rightOutput, \"10\")\n        \nlet output: String = try await fork.merged { bool, string in\n    if bool {\n        return string + string\n    }\n        \n    return string\n}\n        \nlet output = await mergedFork()\n\nXCTAssertEqual(output, \"1010\")\n```\n\n## ForkedArray Example\n\nA ForkedArray makes it easy to perform an asynchronous function on all of the elements in an Array. ForkedArray helps with the [example](#why-use-fork) above.\n\n```swift\nlet forkedArray = ForkedArray(photoNames, map: downloadPhoto(named:))\nlet photos = try await forkedArray.output()\n```\n\n## BatchedForkedArray \n\nThe `BatchedForkedArray` allows you to efficiently parallelize and batch process an array of values using an async function. It provides methods for both resolving the parallelized array in a single output as well as streaming the batches of the resolved array.\n\n\n```swift\nlet batchedForkedArray = BatchedForkedArray(photoNames, batch: 3, map: downloadPhoto(named:))\nlet photos = try await forkedArray.output()\n```\n\nIn the above example, we create an instance of `BatchedForkedArray` with a batch size of 3 and the downloadPhoto function as the map closure.\n\nTo resolve the batched array, we use the `output()` method, which executes the downloadPhoto function on each batch of photo names in parallel. After the resolution is complete, the `photos` array will contain the downloaded photos in the order they were processed.\n\n\n```swift\nlet photoNames = [Int](0 ..\u003c 100)\n\nlet batchedForkedArray = BatchedForkedArray(\n    photoNames,\n    batch: 5,\n    map: downloadPhoto(named:)\n)\n\nfor try await batch in batchedForkedArray.stream() {\n    for photo in batch {\n        // Perform operations on each photo in the batch\n        print(photo)\n    }\n}\n```\n\nIn this example, we create an instance of `BatchedForkedArray` with a batch size of 5 and the `downloadPhoto(named:)` function as the map closure. By using the `stream()` method, we can iterate over batches of photo names asynchronously.\n\nWithin the for-await loop, each batch of photo names is processed asynchronously. We then iterate over each photo in the batch and perform operations accordingly. This allows for efficient processing of large datasets in batches while controlling the number of parallel processes running at once.\n\n## ForkedActor Example\n\n```swift\nactor TestActor {\n    var value: Int = 0\n    \n    func increment() {\n        value += 1\n    }\n}\n\nlet forkedActor = ForkedActor(\n    actor: TestActor(),\n    leftOutput: { actor in\n        await actor.increment()\n    },\n    rightOutput: { actor in\n        try await actor.fork(\n            leftOutput: { await $0.increment() },\n            rightOutput: { await $0.increment() }\n        )\n        .act()\n    }\n)\n\nlet actorValue = await forkedActor.act().value\n\nXCTAssertEqual(actorValue, 3)\n```\n\n### ForkedActor KeyPathActor\u003cInt\u003e Example\n\n```swift\nlet forkedActor = ForkedActor(\n    value: 0,\n    leftOutput: { actor in\n        await actor.update(to: { $0 + 1 })\n    },\n    rightOutput: { actor in\n        try await actor.fork(\n            leftOutput: { actor in\n                await actor.update(to: { $0 + 1 })\n            },\n            rightOutput: { actor in\n                await actor.update(\\.self, to: { $0 + 1 })\n            }\n        )\n        .act()\n    }\n)\n\nlet actorValue = try await forkedActor.act().value\n\nXCTAssertEqual(actorValue, 3)\n```\n\n## Extra Examples\n\n- [Vapor ForkedActor Example](https://github.com/0xLeif/VaporForkDemo)\n- [ForkedArray Pictures Example](https://github.com/0xLeif/ForkedArrayPicturesExample)\n\n## Swift Packages using Fork\n\n### [SwishXCAssets](https://github.com/FullQueueDeveloper/SwishXCAssets)\n\nConcurrently generates an App Icon for your iOS app from an SVG, in the accompanying XCAssets file structure. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xleif%2Ffork","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xleif%2Ffork","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xleif%2Ffork/lists"}