{"id":13800320,"url":"https://github.com/ijoshsmith/function-composition-in-swift","last_synced_at":"2026-02-11T20:24:24.338Z","repository":{"id":141387881,"uuid":"79632472","full_name":"ijoshsmith/function-composition-in-swift","owner":"ijoshsmith","description":"An interactive introduction to function composition in Swift 3.","archived":false,"fork":false,"pushed_at":"2017-01-21T17:45:15.000Z","size":32,"stargazers_count":59,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-22T12:31:11.106Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ijoshsmith.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-01-21T08:02:26.000Z","updated_at":"2024-08-04T00:04:56.881Z","dependencies_parsed_at":null,"dependency_job_id":"cb7f6153-cdf9-4b97-a928-558db4ce4c23","html_url":"https://github.com/ijoshsmith/function-composition-in-swift","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijoshsmith%2Ffunction-composition-in-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijoshsmith%2Ffunction-composition-in-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijoshsmith%2Ffunction-composition-in-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ijoshsmith%2Ffunction-composition-in-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ijoshsmith","download_url":"https://codeload.github.com/ijoshsmith/function-composition-in-swift/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253913103,"owners_count":21983258,"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":[],"created_at":"2024-08-04T00:01:11.404Z","updated_at":"2026-02-11T20:24:24.309Z","avatar_url":"https://github.com/ijoshsmith.png","language":"Swift","funding_links":[],"categories":["Theoretical Computer Science"],"sub_categories":["Functional Reactive Programming"],"readme":"# Function Composition in Swift\n\nThis exploration of function composition in Swift is also available in an Xcode playground, which allows you to experiment with the presented code and immediately see the result. The playground is included in this repository.\n\n## Getting started\nSuppose you need to process comma-separated values. You receive some CSV text and need to only keep the rows that contain exactly three values. Here is the CSV text to process.\n```\nAce,Ale,Are\nBag,Beg,Bug\nCat,Cut\n```\nThe last row (Cat,Cut) is invalid because it has two values.\n\nThe following functions would do the trick.\n```swift\nimport Foundation\n\nfunc splitLines(ofText text: String) -\u003e [String] {\n    return text.components(separatedBy: .newlines)\n}\n\nfunc createRows(fromLines lines: [String]) -\u003e [[String]] {\n    return lines.map { line in\n        line.components(separatedBy: \",\")\n    }\n}\n\nfunc removeInvalidRows(fromRows rows: [[String]]) -\u003e [[String]] {\n    return rows.filter { row in\n        row.count == 3\n    }\n}\n```\nWeaving these functions together might look something like this.\n```swift\nlet lines = splitLines(ofText: csv)\nlet rows = createRows(fromLines: lines)\nlet validRows = removeInvalidRows(fromRows: rows)\n```\nIt can be tempting to remove temporary variables that store each return value, but that often makes the code harder to understand.\n```swift\nlet huh = removeInvalidRows(fromRows: createRows(fromLines: splitLines(ofText: csv)))\n```\nThe code above is somewhat confusing because the functions appear in the opposite order from which they run. It's like reading a story backwards. It'd be nice to avoid extra variables without adding extra weirdness.\n\nWhat's a Swift developer to do?\n\n## Function composition\nLet's take a page from the functional programmer's book and use function composition! Composing two functions yields a new function, which wraps them both up. It's simple. The following code creates a new operator that we can put between two functions and it composes a new function. Ignore the `AdditionPrecedence` bit for now.\n```swift\ninfix operator --\u003e :AdditionPrecedence\n\nfunc --\u003e \u003cA, B, C\u003e (\n    aToB: @escaping (A) -\u003e B,\n    bToC: @escaping (B) -\u003e C)\n    -\u003e (A) -\u003e C\n{\n    return { a in\n        let b = aToB(a)\n        let c = bToC(b)\n        return c\n    }\n}\n```\nIf you're new to Swift this might seem complicated, but it's not, I promise. The operator function name is `--\u003e` and it has three type parameters: `A`, `B`, and `C`. It also has two parameters which happen to be closures (i.e. functions). The first closure turns an `A` into a `B`, and the second closure turns a `B` into a `C`. The `--\u003e` operator returns a new function that transforms an `A` into a `C` and returns it.\n \nNow let's see the CSV example from before rewritten to use function composition.\n```swift\nlet processCSV = splitLines(ofText:) --\u003e createRows(fromLines:) --\u003e removeInvalidRows(fromRows:)\nlet validRows = processCSV(csv)\n```\nThat's pretty nice! The `processCSV` function is the result of composing three functions. The composed functions run in the same order they appear when defining `processCSV`. Note that the `--\u003e` operator is left associative, meaning the functions compose in left-to-right order. This is why I used `AdditionPrecedence` when declaring the operator. It means `--\u003e` has the same precedence and associativity as the standard `+` operator.\n\n## Allowing for side effects\nThis all seems fine and dandy, but does it make debugging tricky? How can you inspect a function's return value when using function composition? One approach is to add logging statements to the functions being composed, but that only works if you can edit those functions, which isn't always the case. There's a better way, check it out.\n```swift\nfunc --\u003e \u003cA, B\u003e (\n    aToB: @escaping (A) -\u003e B,\n    sideEffect: @escaping (B) -\u003e Void)\n    -\u003e (A) -\u003e B\n{\n    return { a in\n        let b = aToB(a)\n        sideEffect(b)\n        return b\n    }\n}\n```\nThis overload of the `--\u003e` operator function gives special treatment to a special case. When the function on the righthand side returns `Void`, it is assumed to exist only to produce a side effect, such as logging to the console or saving data to a file. In the world of functional programming this is considered an \"impure\" function, because it does not operate only on its inputs, and therefore can have unpredictable side effects. \n\nHere's how we can use this new version of the `--\u003e` function to perform logging.\n```swift\nlet processCSVWithLogging = splitLines(ofText:)\n    --\u003e { print(\"lines: \\($0)\") }\n    --\u003e createRows(fromLines:)\n    --\u003e { print(\"rows: \\($0)\") }\n    --\u003e removeInvalidRows(fromRows:)\n    --\u003e { print(\"valid rows: \\($0)\") }\n\nlet validRows = processCSVWithLogging(csv)\n```\n## Optional chaining\nThe function composition operator works fine with optional values, since `Optional\u003cT\u003e` is a Swift enum and can be used in a generic function just like any other type. But it would be nice if there was extra support for working with optional values when using function composition, similar to the optional chaining feature in Swift. For example, `account.emergencyContact?.sendAlert()` will only send an alert to an emergency contact if one exists.\n\nHere is a new variant of the function composition operator that supports optional chaining. The function on the left returns an optional value, and the function on the right will only be called if that value is non-nil.\n```swift\ninfix operator --\u003e? :AdditionPrecedence\n\nfunc --\u003e? \u003cA, B, C\u003e (\n    aToB: @escaping (A) -\u003e B?,\n    bToC: @escaping (B) -\u003e C?)\n    -\u003e (A) -\u003e C?\n{\n    return { a in\n        guard let b = aToB(a) else { return nil }\n        let c = bToC(b)\n        return c\n    }\n}\n```\nLet's see this new `--\u003e?` operator in action…\n```swift\nimport UIKit\n\nfunc url(forCompany stockSymbol: String) -\u003e URL? {\n    let companyMap = [\"AAPL\":  \"http://apple.com\",\n                      \"GOOGL\": \"http://google.com\",\n                      \"MSFT\":  \"http://microsoft.com\"]\n    guard let path = companyMap[stockSymbol] else { return nil }\n    return URL(string: path)\n}\n\nfunc data(fromURL url: URL) -\u003e Data? {\n    return try? Data(contentsOf: url)\n}\n\nfunc attributedHTML(withData data: Data) -\u003e NSAttributedString? {\n    return try? NSAttributedString(data: data,\n                                   options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],\n                                   documentAttributes: nil)\n}\n\nlet attributedHTMLForCompany = url(forCompany:) --\u003e? data(fromURL:) --\u003e? attributedHTML(withData:)\n\nlet html = attributedHTMLForCompany(\"AAPL\")\n```\nIf any of the functions return `nil`, none of the subsequent functions will be called. This is a nice Swifty addition to our  function composition toolbox. Remember, the `--\u003e` and `--\u003e?` operators can both be used when forming larger statements.\n## Passing multiple arguments\nAll of the examples so far have composed functions with just one parameter, but it's possible to\nuse functions with multiple parameters. One way this can be accomplished is to wrap the function \ncall in a closure, as seen in the following example.\n```swift\nfunc getHour(fromDate date: Date) -\u003e Int {\n    return Calendar.current.component(.hour, from: date)\n}\n\nfunc isHour(_ hour: Int, between startHour: Int, and endHour: Int) -\u003e Bool {\n    return (startHour...endHour).contains(hour)\n}\n\nlet isWorkHour = getHour(fromDate:) --\u003e { isHour($0, between: 9, and: 17) }\n\nlet now = Date()\nlet shouldBeAtWork = isWorkHour(now)\n```\n## Have fun\nThat's all for this quick excursion into the world of function composition. It's definitely not a silver bullet, but it can lead to code that is easier to read and understand. Perhaps this style of coding feels right to you. If so, give it a try!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fijoshsmith%2Ffunction-composition-in-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fijoshsmith%2Ffunction-composition-in-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fijoshsmith%2Ffunction-composition-in-swift/lists"}