{"id":37029463,"url":"https://github.com/unruly/control","last_synced_at":"2026-01-14T03:32:20.601Z","repository":{"id":57716356,"uuid":"79576849","full_name":"unruly/control","owner":"unruly","description":"A collection of functional control-flow primitives and utilities.","archived":true,"fork":false,"pushed_at":"2020-09-30T10:27:38.000Z","size":233,"stargazers_count":10,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-09T03:40:43.109Z","etag":null,"topics":["functional-programming","java","railway-oriented-programming"],"latest_commit_sha":null,"homepage":"https://www.javadoc.io/doc/co.unruly/control","language":"Java","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/unruly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-20T16:41:39.000Z","updated_at":"2023-03-13T10:33:45.000Z","dependencies_parsed_at":"2022-08-26T13:11:54.868Z","dependency_job_id":null,"html_url":"https://github.com/unruly/control","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/unruly/control","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unruly%2Fcontrol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unruly%2Fcontrol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unruly%2Fcontrol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unruly%2Fcontrol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unruly","download_url":"https://codeload.github.com/unruly/control/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unruly%2Fcontrol/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["functional-programming","java","railway-oriented-programming"],"created_at":"2026-01-14T03:32:19.800Z","updated_at":"2026-01-14T03:32:20.591Z","avatar_url":"https://github.com/unruly.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# co.unruly.control\n\n[![Build Status](https://travis-ci.org/unruly/control.svg?branch=master)](https://travis-ci.org/unruly/control)\n[![Release Version](https://img.shields.io/maven-central/v/co.unruly/control.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22co.unruly%22%20AND%20a%3A%22control%22)\n[![Javadocs](https://www.javadoc.io/badge/co.unruly/control.svg)](https://www.javadoc.io/doc/co.unruly/control)\n\n:warning: **This repo had been archived.  Development continues at https://github.com/writeoncereadmany/control** :warning:\n\n\nControl is a collection of functional control-flow primitives and utilities, built around a \n[`Result`](https://javadoc.io/page/co.unruly/control/latest/co/unruly/control/result/Result.html) type.\n\n## Installation\n\n### Using Maven\n\nAdd this dependency to your `pom.xml`.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eco.unruly\u003c/groupId\u003e\n    \u003cartifactId\u003econtrol\u003c/artifactId\u003e\n    \u003cversion\u003e0.8.12\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Documentation\n\n- [JavaDoc](https://www.javadoc.io/doc/co.unruly/control)\n\n## Examples\n\n### Result\n\nWhat is a `Result`? It's a representation of the outcome of an operation *which may have failed*. Kind of like `java.util.Optional`.\n\nLike `Optional`, it wraps a value, and like `Optional`, it doesn't allow you to use that value directly. \nAfter all, if it's representing a failed operation, there may not *be* a value.\nIn order to extract a value, you have to define how to handle the failure cases.\n\nHowever, an `Optional` is either *present*, and contains a value, or *absent*, conveying no information. \nA `Result`, however, is either a *success*, containing a value, or a *failure*, containing information about the failure.\n\n\n#### Creating a Result from a value: Introducers\n\nThe simplest way to create a `Result` is simply to instantiate either a success or failure value, as appropriate.\nFor example, the following code creates a successful `Result`, followed by a failed `Result`.\n\n```java\nResult\u003cInteger, String\u003e firstResult = Result.success(42);\nResult\u003cInteger, String\u003e secondResult = Result.failure(\"What is six times nine?\");\n```\n\nThere are also many common repeated patterns of operations which can fail. \nFor example, you may have some code which creates `Optional`s, which you would like \nto convert to `Result`s in order to better track failure causes. \n\nFor this, [`co.unruly.control.result.Introducers`](https://javadoc.io/page/co.unruly/control/latest/co/unruly/control/result/Introducers.html) \ncontains a selection of useful functions which yield `Result`s:\n\n```java\npublic Result\u003cInteger, String\u003e asResult(Optional\u003cInteger\u003e maybeNumber) {\n    return with(maybeNumber, fromOptional(() -\u003e \"No number found. :(\"));    \n}\n```\n\n#### Composing operations on a Result: Transformers\n\n`Result` is most useful when modelling an operation as a sequence of smaller operations, each using\nthe output of the last step, and some of which can fail. The most common operations here are `onSuccess()` \nand `attempt()`.\n\nInvoking `onSuccess(f)` on a `Result` will apply `f` to the value in the `Result` if it's a success, yielding a \nnew success. If the original `Result` was a failure, `f` will not be invoked and the original failing `Result` will\nbe returned.\n\nIf you have a function `f` which can fail - i.e. a function which returns a `Result` - then invoking `attempt(f)` will\napply `f` to the value in the `Result` if it's a success, yielding a new `Result` which may be either a success or \nfailure. If the original `Result` was a failure, `f` will not be invoked and the original failing `Result` will\nbe returned.\n\nThis allows us to chain together a sequence of calls to `onSuccess` and `attempt` to specify what to do on the\nhappy path, and cascade any failure cases together to be handled once.\n\nFor example, if we want to write a bestselling novel, then there are various steps towards getting it published. \nWe need to have an idea, secure an advance, write the manuscript, get it edited, get it published, and rocket up the bestseller charts.\n\nGetting the idea, securing an advance and finishing the manuscript are definitely steps which can fail.\nHowever, given success in the other steps, editing and publishing are mechanical steps we can have confidence will succeed, \nand once those are complete we can then release the novel and (eventually) count the total sales.\n\nIf any of the steps fail, no novel is published, and we can look at the error message from whenever the process failed.\n\nWe could therefore model the process as follows:\n\n```java\npublic static String describeNovelSales(Author author, Publisher publisher, Editor editor, Retailer retailer) {\n    return author.getIdea()\n            .then(attempt(publisher::getAdvance))\n            .then(attempt(author::writeNovel))\n            .then(onSuccess(editor::editNovel))\n            .then(onSuccess(publisher::publishNovel))\n            .then(onSuccess(retailer::sellNovel))\n            .then(onSuccess(sales -\u003e format(\"%s sold %d copies\", sales.novel, sales.copiesSold)))\n            .then(collapse());\n}\n```\n\nWhilst `onSuccess()` and `attempt()` are the most common ways to transform a `Result` into another `Result`,\nother use cases exist. A collection of such functions exists in \n[`co.unruly.control.result.Transformers`](https://javadoc.io/page/co.unruly/control/latest/co/unruly/control/result/Transformers.html).\n\n#### Extracting a value from a Result: Resolvers\n\nThe simplest way to extract a value from a `Result` is to simply describe what to do with a failure value.\nFor example, the following method takes a `Result` of either an `Integer` \nor a `String` describing the failure, and returns the wrapped integer if it was a success,\nor -1 if it was a failure:\n\n```java\npublic Integer extractValue(Result\u003cInteger, String\u003e count) {\n    return count.then(ifFailed(x -\u003e -1));\n}\n```\n\nSometimes, the success and failure types are the same. In that case, you can simply\ncollapse both cases to a single value:\n\n```java\npublic static void main(String ...args) {\n    printResult(Result.success(\"This was a triumph!\"));\n    printResult(Result.failure(\"The cake is a lie\"));\n}\n\n\npublic static String printResult(Result\u003cString, String\u003e value) {\n    System.out.println(value.then(collapse()));\n}\n```\nWhich outputs:\n```\n\u003e This was a triumph!\n\u003e The cake is a lie\n```\n\n`ifFailed` and `collapse` can both be found in \n[`co.unruly.control.result.Resolvers`](https://javadoc.io/page/co.unruly/control/latest/co/unruly/control/result/Resolvers.html)\n, along with a selection of other functions to convert `Result`s into non-`Result` values.\n\n#### Functional API\n\nNone of the operations described above are methods on the `Result` object. \nInstead, `Result` presents only two methods: `Result.either()` and `Result.then()`.\n\n##### Result.either()\n\n`Result.either` takes two functions, one for the success case and one for the failure case.\nIf the `Result` is a success, it applies the first function to its success value and returns the result.\nIf it was a failure, instead it applies the second function.\n\n```java\npublic static void main(String ...args) {\n    printResult(Result.success(99));\n    printResult(Result.failure(\"bananas\"));\n}\n\n\npublic static String printResult(Result\u003cString, String\u003e value) {\n    System.out.println(value.either(\n        success -\u003e success + \" bottles of beer on the wall\",\n        failure -\u003e \"Yes sir, we have no \" + failure    \n    ));\n}\n```\n\nWhich outputs:\n```\n\u003e 99 bottles of beer on the wall\n\u003e Yes sir, we have no bananas\n```\n\nNote that the argument types of the functions must match the corresponding success\nand failure types, and both functions must return the same type.\n\n##### Result.then()\n\n`Result.then` takes a function from a `Result` to a value, and then applies\nthat function to the result. The following lines of code are (other than generics inference issues) equivalent:\n```java\nifFailed(x -\u003e \"Hello World\").apply(result);\nresult.then(ifFailed(x -\u003e \"Hello World\"));\n```\n\nBy structuring the API like this, instead of having a fixed set of methods available, we can\ncreate more specialised functions for domain-specific use cases and mix them seamlessly with \nstandard operations, and customise how we interact with `Result`s.\n\nThe functions included in this library are all higher-order functions, returning\ninstances of `Function`. This includes functions to create `Result`s from non-`Result`\nvalues - both for consistency, and compatibility with idiomatic `Stream` usage. \nAs non-`Result` values don't implement `then()`, and there are both readability and\ngenerics issues, we also include `Piper`.\n\n##### Piper\n\nThis approach is slightly more cumbersome than it could be, as Java's ability to infer \ngeneric types is not particularly great. For example, the following code doesn't compile:\n\n```java\npublic Result\u003cInteger, String\u003e asResult(Optional\u003cInteger\u003e maybeNumber) {\n    return fromOptional(() -\u003e \"No number found. :(\").apply(maybeNumber);    \n}\n```\nThis fails to compile because the type of `fromOptional` is inferred to be \n`Function\u003cOptional\u003cObject\u003e, Result\u003cObject, String\u003e`, because the type inference \nengine can't infer the success type based on subsequent operations (namely, the type\npassed to `apply()`).\n\nEnter [`Piper`](https://javadoc.io/page/co.unruly/control/latest/co/unruly/control/Piper.html). \nA `Piper` is simply a box for a value: \n\n```java\nPiper\u003cInteger\u003e pipe = Piper.pipe(42);\n```\n\nYou can then chain functions on the pipe:\n\n```java\nPiper.pipe(42)              // yields a Pipe containing 42\n     .then(x -\u003e x + 10)     // yields a Pipe containing 52\n     .then(x -\u003e x * 2)      // yields a Pipe containing 104\n     .resolve()             // returns 104\n```\n\nThis allows us to rewrite our failing-to-compile example from above as:\n\n```java\npublic Result\u003cInteger, String\u003e asResult(Optional\u003cInteger\u003e maybeNumber) {\n    return pipe(maybeNumber)                                // Pipe of Optional\u003cInteger\u003e\n        .then(fromOptional(() -\u003e \"No number found. :(\"))    // Pipe of Result\u003cInteger, String\u003e\n        .resolve();                                         // Result\u003cInteger, String\u003e               \n}\n```\n\nFor short examples like this, there's also a `resolveWith()` method:\n\n```java\npublic Result\u003cInteger, String\u003e asResult(Optional\u003cInteger\u003e maybeNumber) {\n    return pipe(maybeNumber)                                \n        .resolveWith(fromOptional(() -\u003e \"No number found. :(\"));\n}\n```\n\n`Piper.then()` functions in the same way as `Result.then()`: it applies the provided function\nto its contents. This means we can build a functional pipeline which includes both regular \nvalues and Results:\n\n```java\nResult\u003cInteger, String\u003e result = pipe(\"a=1234;\")                            // Piper\u003cString\u003e\n        .then(pattern::matcher)                                             // Piper\u003cMatcher\u003e            \n        .then(ifIs(Matcher::find, m -\u003e m.group(1)))                         // Piper\u003cResult\u003cString, Matcher\u003e\u003e\n        .then(onFailure(__ -\u003e \"Could not find group to match\"))             // Piper\u003cResult\u003cString, String\u003e\u003e\n        .then(attempt(tryTo(Integer::parseInt, ex -\u003e ex.getMessage())))     // Piper\u003cResult\u003cInteger, String\u003e\u003e\n        .resolve();                                                         // Result\u003cInteger, String\u003e\n```\n\nNote that the use of `Piper.then()` is identical to the way we'd use `Result.then()`, except now we\ncan directly chain from non-failable states into failable states. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funruly%2Fcontrol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funruly%2Fcontrol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funruly%2Fcontrol/lists"}