{"id":13871869,"url":"https://github.com/pointfreeco/swift-overture","last_synced_at":"2025-05-15T15:08:31.124Z","repository":{"id":45695821,"uuid":"128791170","full_name":"pointfreeco/swift-overture","owner":"pointfreeco","description":"🎼 A library for function composition.","archived":false,"fork":false,"pushed_at":"2024-07-05T17:46:54.000Z","size":168,"stargazers_count":1143,"open_issues_count":1,"forks_count":59,"subscribers_count":25,"default_branch":"main","last_synced_at":"2025-04-02T22:18:38.454Z","etag":null,"topics":["function-composition","functional-programming"],"latest_commit_sha":null,"homepage":"https://www.pointfree.co/episodes/ep11-composition-without-operators","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/pointfreeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","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":"2018-04-09T15:12:57.000Z","updated_at":"2025-03-09T05:11:55.000Z","dependencies_parsed_at":"2025-01-18T18:07:53.879Z","dependency_job_id":"251db62d-1562-4f9d-8919-3f27a222c3e2","html_url":"https://github.com/pointfreeco/swift-overture","commit_stats":{"total_commits":61,"total_committers":18,"mean_commits":3.388888888888889,"dds":"0.42622950819672134","last_synced_commit":"912dce67981da3c228b9244a7af8dbdab91afd41"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-overture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-overture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-overture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-overture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pointfreeco","download_url":"https://codeload.github.com/pointfreeco/swift-overture/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247721898,"owners_count":20985084,"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":["function-composition","functional-programming"],"created_at":"2024-08-05T23:00:28.802Z","updated_at":"2025-04-07T20:11:17.006Z","avatar_url":"https://github.com/pointfreeco.png","language":"Swift","funding_links":[],"categories":["HarmonyOS","Library","\u003ca name=\"for-projects\"\u003e\u003c/a\u003e For Projects","Swift"],"sub_categories":["Windows Manager","\u003ca name=\"for-projects-example-projects\"\u003e\u003c/a\u003e Example Projects"],"readme":"# 🎼 Overture\n\n[![CI](https://github.com/pointfreeco/swift-overture/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-overture/goto)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-overture%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pointfreeco/swift-overture)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-overture%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pointfreeco/swift-overture)\n\nA library for function composition.\n\n## Table of Contents\n\n  - [Motivation](#motivation)\n  - [Examples](#examples)\n      - [`pipe`](#pipe)\n      - [`with` and `update`](#with-and-update)\n      - [`concat`](#concat)\n      - [`curry`, `flip`, and `zurry`](#curry-flip-and-zurry)\n      - [`get`](#get)\n      - [`prop`](#prop)\n      - [`over` and `set`](#over-and-set)\n      - [`mprop`, `mver`, and `mut`](#mprop-mver-and-mut)\n      - [`zip`](#zip-and-zipwith)\n  - [FAQ](#faq)\n  - [Installation](#installation)\n  - [🎶 Prelude](#-prelude)\n  - [Interested in learning more?](#interested-in-learning-more)\n  - [License](#license)\n\n## Motivation\n\nWe work with functions all the time, but function composition is hiding in plain sight!\n\nFor instance, we work with functions when we use higher-order methods, like `map` on arrays:\n\n``` swift\n[1, 2, 3].map { $0 + 1 }\n// [2, 3, 4]\n```\n\nIf we wanted to modify this simple closure to square our value after incrementing it, things begin to get messy.\n\n``` swift\n[1, 2, 3].map { ($0 + 1) * ($0 + 1) }\n// [4, 9, 16]\n```\n\nFunctions allow us to identify and extract reusable code. Let's define a couple functions that make up the behavior above.\n\n``` swift\nfunc incr(_ x: Int) -\u003e Int {\n  return x + 1\n}\n\nfunc square(_ x: Int) -\u003e Int {\n  return x * x\n}\n```\n\nWith these functions defined, we can pass them directly to `map`!\n\n``` swift\n[1, 2, 3]\n  .map(incr)\n  .map(square)\n// [4, 9, 16]\n```\n\nThis refactor reads much better, but it's less performant: we're mapping over the array twice and creating an intermediate copy along the way! While we could use `lazy` to fuse these calls together, let's take a more general approach: function composition!\n\n``` swift\n[1, 2, 3].map(pipe(incr, square))\n// [4, 9, 16]\n```\n\nThe `pipe` function glues other functions together! It can take more than two arguments and even change the type along the way!\n\n``` swift\n[1, 2, 3].map(pipe(incr, square, String.init))\n// [\"4\", \"9\", \"16\"]\n```\n\nFunction composition lets us build new functions from smaller pieces, giving us the ability to extract and reuse logic in other contexts.\n\n``` swift\nlet computeAndStringify = pipe(incr, square, String.init)\n\n[1, 2, 3].map(computeAndStringify)\n// [\"4\", \"9\", \"16\"]\n\ncomputeAndStringify(42)\n// \"1849\"\n```\n\nThe function is the smallest building block of code. Function composition gives us the ability to fit these blocks together and build entire apps out of small, reusable, understandable units.\n\n## Examples\n\n### `pipe`\n\nThe most basic building block in Overture. It takes existing functions and smooshes them together. That is, given a function `(A) -\u003e B` and a function `(B) -\u003e C`, `pipe` will return a brand new `(A) -\u003e C` function.\n\n``` swift\nlet computeAndStringify = pipe(incr, square, String.init)\n\ncomputeAndStringify(42)\n// \"1849\"\n\n[1, 2, 3].map(computeAndStringify)\n// [\"4\", \"9\", \"16\"]\n```\n\n### `with` and `update`\n\nThe `with` and `update` functions are useful for applying functions to values. They play nicely with the `inout` and mutable object worlds, wrapping otherwise imperative configuration statements in an expression.\n\n``` swift\nclass MyViewController: UIViewController {\n  let label = updateObject(UILabel()) {\n    $0.font = .systemFont(ofSize: 24)\n    $0.textColor = .red\n  }\n}\n```\n\nAnd it restores the left-to-right readability we're used to from the method world.\n\n``` swift\nwith(42, pipe(incr, square, String.init))\n// \"1849\"\n```\n\nUsing an `inout` parameter.\n\n``` swift\nupdate(\u0026user, mut(\\.name, \"Blob\"))\n```\n\n### `concat`\n\nThe `concat` function composes with single types. This includes composition of the following function signatures:\n\n- `(A) -\u003e A`\n- `(inout A) -\u003e Void`\n- `\u003cA: AnyObject\u003e(A) -\u003e Void`\n\nWith `concat`, we can build powerful configuration functions from small pieces.\n\n``` swift\nlet roundedStyle: (UIView) -\u003e Void = {\n  $0.clipsToBounds = true\n  $0.layer.cornerRadius = 6\n}\n\nlet baseButtonStyle: (UIButton) -\u003e Void = {\n  $0.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)\n  $0.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)\n}\n\nlet roundedButtonStyle = concat(\n  baseButtonStyle,\n  roundedStyle\n)\n\nlet filledButtonStyle = concat(roundedButtonStyle) {\n  $0.backgroundColor = .black\n  $0.tintColor = .white\n}\n\nlet button = with(UIButton(type: .system), filledButtonStyle)\n```\n\n### `curry`, `flip`, and `zurry`\n\nThese functions make up the [Swiss army knife](https://www.pointfree.co/episodes/ep5-higher-order-functions) of composition. They give us the power to take existing functions and methods that don't compose (_e.g_, those that take zero or multiple arguments) and restore composition.\n\nFor example, let's transform a string initializer that takes multiple arguments into something that can compose with `pipe`.\n\n``` swift\nString.init(data:encoding:)\n// (Data, String.Encoding) -\u003e String?\n```\n\nWe use `curry` to transform multi-argument functions into functions that take a single input and return new functions to gather more inputs along the way.\n\n``` swift\ncurry(String.init(data:encoding:))\n// (Data) -\u003e (String.Encoding) -\u003e String?\n```\n\nAnd we use `flip` to flip the order of arguments. Multi-argument functions and methods typically take data first and configuration second, but we can generally apply configuration before we have data, and `flip` allows us to do just that.\n\n``` swift\nflip(curry(String.init(data:encoding:)))\n// (String.Encoding) -\u003e (Data) -\u003e String?\n```\n\nNow we have a highly-reusable, composable building block that we can use to build pipelines.\n\n``` swift\nlet stringWithEncoding = flip(curry(String.init(data:encoding:)))\n// (String.Encoding) -\u003e (Data) -\u003e String?\n\nlet utf8String = stringWithEncoding(.utf8)\n// (Data) -\u003e String?\n```\n\nSwift also exposes methods as static, unbound functions. These functions are already in curried form. All we need to do is `flip` them to make them more useful!\n\n``` swift\nString.capitalized\n// (String) -\u003e (Locale?) -\u003e String\n\nlet capitalized = flip(String.capitalized)\n// (Locale?) -\u003e (String) -\u003e String\n\n[\"hello, world\", \"and good night\"]\n  .map(capitalized(Locale(identifier: \"en\")))\n// [\"Hello, World\", \"And Good Night\"]\n```\n\nAnd `zurry` restores composition for functions and methods that take zero arguments.\n\n``` swift\nString.uppercased\n// (String) -\u003e () -\u003e String\n\nflip(String.uppercased)\n// () -\u003e (String) -\u003e String\n\nlet uppercased = zurry(flip(String.uppercased))\n// (String) -\u003e String\n\n[\"hello, world\", \"and good night\"]\n  .map(uppercased)\n// [\"HELLO, WORLD\", \"AND GOOD NIGHT\"]\n```\n\n### `get`\n\nThe `get` function produces [getter functions](https://www.pointfree.co/episodes/ep8-getters-and-key-paths) from key paths.\n\n``` swift\nget(\\String.count)\n// (String) -\u003e Int\n\n[\"hello, world\", \"and good night\"]\n  .map(get(\\.count))\n// [12, 14]\n```\n\nWe can even compose other functions into `get` by using the `pipe` function. Here we build a function that increments an integer, squares it, turns it into a string, and then gets the string's character count:\n\n```swift\npipe(incr, square, String.init, get(\\.count))\n// (Int) -\u003e Int\n```\n\n### `prop`\n\nThe `prop` function produces [setter functions](https://www.pointfree.co/episodes/ep7-setters-and-key-paths) from key paths.\n\n``` swift\nlet setUserName = prop(\\User.name)\n// ((String) -\u003e String) -\u003e (User) -\u003e User\n\nlet capitalizeUserName = setUserName(capitalized(Locale(identifier: \"en\")))\n// (User) -\u003e User\n\nlet setUserAge = prop(\\User.age)\n\nlet celebrateBirthday = setUserAge(incr)\n// (User) -\u003e User\n\nwith(User(name: \"blob\", age: 1), concat(\n  capitalizeUserName,\n  celebrateBirthday\n))\n// User(name: \"Blob\", age: 2)\n```\n\n### `over` and `set`\n\nThe `over` and `set` functions produce `(Root) -\u003e Root` transform functions that work on a `Value` in a structure given a key path (or [setter function](https://www.pointfree.co/episodes/ep7-setters-and-key-paths)).\n\nThe `over` function takes a `(Value) -\u003e Value` transform function to modify an existing value.\n\n``` swift\nlet celebrateBirthday = over(\\User.age, incr)\n// (User) -\u003e User\n```\n\nThe `set` function replaces an existing value with a brand new one.\n\n```swift\nwith(user, set(\\.name, \"Blob\"))\n```\n\n### `mprop`, `mver`, and `mut`\n\nThe `mprop`, `mver` and `mut` functions are _mutable_ variants of `prop`, `over` and `set`.\n\n```swift\nlet guaranteeHeaders = mver(\\URLRequest.allHTTPHeaderFields) { $0 = $0 ?? [:] }\n\nlet setHeader = { name, value in\n  concat(\n    guaranteeHeaders,\n    { $0.allHTTPHeaderFields?[name] = value }\n  )\n}\n\nlet request = update(\n  URLRequest(url: url),\n  mut(\\.httpMethod, \"POST\"),\n  setHeader(\"Authorization\", \"Token \" + token),\n  setHeader(\"Content-Type\", \"application/json; charset=utf-8\")\n)\n```\n\n### `zip` and `zip(with:)`\n\nThis is a function that Swift ships with! Unfortunately, it's limited to pairs of sequences. Overture defines `zip` to work with up to ten sequences at once, which makes combining several sets of related data a snap.\n\n```swift\nlet ids = [1, 2, 3]\nlet emails = [\"blob@pointfree.co\", \"blob.jr@pointfree.co\", \"blob.sr@pointfree.co\"]\nlet names = [\"Blob\", \"Blob Junior\", \"Blob Senior\"]\n\nzip(ids, emails, names)\n// [\n//   (1, \"blob@pointfree.co\", \"Blob\"),\n//   (2, \"blob.jr@pointfree.co\", \"Blob Junior\"),\n//   (3, \"blob.sr@pointfree.co\", \"Blob Senior\")\n// ]\n```\n\nIt's common to immediately `map` on zipped values.\n\n``` swift\nstruct User {\n  let id: Int\n  let email: String\n  let name: String\n}\n\nzip(ids, emails, names).map(User.init)\n// [\n//   User(id: 1, email: \"blob@pointfree.co\", name: \"Blob\"),\n//   User(id: 2, email: \"blob.jr@pointfree.co\", name: \"Blob Junior\"),\n//   User(id: 3, email: \"blob.sr@pointfree.co\", name: \"Blob Senior\")\n// ]\n```\n\nBecause of this, Overture provides a `zip(with:)` helper, which takes a tranform function up front and is curried, so it can be composed with other functions using `pipe`.\n\n``` swift\nzip(with: User.init)(ids, emails, names)\n```\n\nOverture also extends the notion of `zip` to work with optionals! It's an expressive way of combining multiple optionals together.\n\n``` swift\nlet optionalId: Int? = 1\nlet optionalEmail: String? = \"blob@pointfree.co\"\nlet optionalName: String? = \"Blob\"\n\nzip(optionalId, optionalEmail, optionalName)\n// Optional\u003c(Int, String, String)\u003e.some((1, \"blob@pointfree.co\", \"Blob\"))\n```\n\nAnd `zip(with:)` lets us transform these tuples into other values.\n\n``` swift\nzip(with: User.init)(optionalId, optionalEmail, optionalName)\n// Optional\u003cUser\u003e.some(User(id: 1, email: \"blob@pointfree.co\", name: \"Blob\"))\n```\n\nUsing `zip` can be an expressive alternative to `let`-unwrapping!\n\n``` swift\nlet optionalUser = zip(with: User.init)(optionalId, optionalEmail, optionalName)\n\n// vs.\n\nlet optionalUser: User?\nif let id = optionalId, let email = optionalEmail, let name = optionalName {\n  optionalUser = User(id: id, email: email, name: name)\n} else {\n  optionalUser = nil\n}\n```\n\n## FAQ\n\n  - **Should I be worried about polluting the global namespace with free functions?**\n\n    Nope! Swift has several layers of scope to help you here.\n\n      - You can limit exposing highly-specific functions beyond a single file by using `fileprivate` and `private` scope.\n      - You can define functions as `static` members inside types.\n      - You can qualify functions with the module's name (_e.g._, `Overture.pipe(f, g)`). You can even autocomplete free functions from the module's name, so discoverability doesn't have to suffer!\n\n  - **Are free functions that common in Swift?**\n\n    It may not seem like it, but free functions are everywhere in Swift, making Overture extremely useful! A few examples:\n\n      - Initializers, like `String.init`.\n      - Unbound methods, like `String.uppercased`.\n      - Enum cases with associated values, like `Optional.some`.\n      - Ad hoc closures we pass to `map`, `filter`, and other higher-order methods.\n      - Top-level Standard Library functions like `max`, `min`, and `zip`.\n\n## Installation\n\nYou can add Overture to an Xcode project by adding it as a package dependency.\n\n\u003e https://github.com/pointfreeco/swift-overture\n\nIf you want to use Overture in a [SwiftPM](https://swift.org/package-manager/) project, it's as simple as adding it to a `dependencies` clause in your `Package.swift`:\n\n``` swift\ndependencies: [\n  .package(url: \"https://github.com/pointfreeco/swift-overture\", from: \"0.5.0\")\n]\n```\n\n## 🎶 Prelude\n\nThis library was created as an alternative to [swift-prelude](https://www.github.com/pointfreeco/swift-prelude), which is an experimental functional programming library that uses infix operators. For example, `pipe` is none other than the arrow composition operator `\u003e\u003e\u003e`, which means the following are equivalent:\n\n```swift\nxs.map(incr \u003e\u003e\u003e square)\nxs.map(pipe(incr, square))\n```\n\nWe know that many code bases are not going to be comfortable introducing operators, so we wanted to reduce the barrier to entry for embracing function composition.\n\n## Interested in learning more?\n\nThese concepts (and more) are explored thoroughly in [Point-Free](https://www.pointfree.co), a video series exploring functional programming and Swift hosted by [Brandon Williams](https://twitter.com/mbrandonw) and [Stephen Celis](https://twitter.com/stephencelis).\n\nThe ideas in this episode were first explored in [Episode #11](https://www.pointfree.co/episodes/ep11-composition-without-operators):\n\n\u003ca href=\"https://www.pointfree.co/episodes/ep11-composition-without-operators\"\u003e\n  \u003cimg alt=\"video poster image\" src=\"https://d1hf1soyumxcgv.cloudfront.net/0011-composition-without-operators/0011-poster.jpg\" width=\"480\"\u003e\n\u003c/a\u003e\n\n## License\n\nAll modules are released under the MIT license. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-overture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpointfreeco%2Fswift-overture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-overture/lists"}