{"id":21040732,"url":"https://github.com/l-briand/either","last_synced_at":"2025-05-15T16:33:25.861Z","repository":{"id":203979765,"uuid":"710842353","full_name":"L-Briand/either","owner":"L-Briand","description":"Either and Option implementation in Kotlin Multiplatform","archived":false,"fork":false,"pushed_at":"2024-07-24T09:52:25.000Z","size":479,"stargazers_count":10,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-03T11:43:48.307Z","etag":null,"topics":["either","kotlin","kotlin-library","kotlin-multiplatform","kotlin-multiplatform-library","kotlinx-serialization","option","optional"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","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/L-Briand.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":"2023-10-27T14:50:40.000Z","updated_at":"2024-07-24T09:52:28.000Z","dependencies_parsed_at":"2024-11-19T13:47:56.777Z","dependency_job_id":"35dd4e9b-c65c-4366-9988-8affc32564f6","html_url":"https://github.com/L-Briand/either","commit_stats":null,"previous_names":["l-briand/either"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/L-Briand%2Feither","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/L-Briand%2Feither/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/L-Briand%2Feither/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/L-Briand%2Feither/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/L-Briand","download_url":"https://codeload.github.com/L-Briand/either/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254377467,"owners_count":22061143,"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":["either","kotlin","kotlin-library","kotlin-multiplatform","kotlin-multiplatform-library","kotlinx-serialization","option","optional"],"created_at":"2024-11-19T13:47:49.104Z","updated_at":"2025-05-15T16:33:24.773Z","avatar_url":"https://github.com/L-Briand.png","language":"Kotlin","readme":"# Kotlin Either \u0026 Option Multiplatform\n\n`Either` and `Option` implementation in kotlin Multiplatform.\n\n---\n\n`Either` is, like the [Result](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/) class in kotlin, a\ndiscriminated union of two types.\nHowever, it lets you use any Type as the second Type.\n\n`Option` is useful when you need to have a third state to your variable, like in a json object where a field 'presence'\nis important. (In most cases, using a nullable variable is fine.)\n\n1. My variable is not here `{}` -\u003e `None`\n2. My variable is here but null `{\"field\":null}` -\u003e `Some(null)`\n3. My variable is here `{\"field\":\"value\"}` -\u003e `Some(\"value\")`\n\n## Import from maven\n\n### Multiplatform\n\n```kotlin\nrepositories {\n    mavenCentral()\n}\nval commonMain by getting {\n    dependencies {\n        implementation(\"net.orandja.kt:either:2.0.0\")\n    }\n}\n```\n\n### Jvm\n\n```kotlin\nrepositories {\n    mavenCentral()\n}\ndependencies {\n    implementation(\"net.orandja.kt:either:2.0.0\")\n}\n```\n\n## Usage\n\n[dokka documentation here](https://l-briand.github.io/either/either/net.orandja.either/index.html)\n\n### Either\n\nThis example is a bit convoluted, but it explains pretty well how to use `Either`.\n\n```kotlin\nenum class ErrCode { CONVERT, TOO_LOW, TOO_HIGH, /* ... */ }\n\n// Get information from somewhere\nvar data: Either\u003cString, ErrCode\u003e = getData()\n\n// Transform left String value to Int\n// Other methods can be used to transform a Either class.\nval transformed: Either\u003cInt?, ErrCode\u003e = data.letLeft { it.toIntOrNull }\n\n// Transform left Int? to Float without exception.\nfun doSomething(value: Either\u003cInt?, ErrCode\u003e): Either\u003cFloat, ErrCode\u003e {\n\n    // A 'require' block do not allow to be ended.\n    // You need to return or throw an exception inside.\n    val percent = value.requireLeft { it: Right\u003cErrCode\u003e -\u003e\n        // Here we return the upper function early, \n        // with the already known error\n        return it\n    }\n\n    percent ?: return Right(ErrCode.CONVERT)\n    if (percent \u003c= 0) return Right(ErrCode.TOO_LOW)\n    if (percent \u003e= 100) return Right(ErrCode.TOO_HIGH)\n    return Left(percent.toFloat() / 100f)\n}\n\n// Show result\nwhen (val result = doSomething(transformed)) {\n    is Left -\u003e println(\"Current progress ${result.left}\")\n    is Right -\u003e println(\"Invalid value. Reason ${result.right}\")\n}\n```\n\nThe `Either` class is serializable with [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization).\n\nIt adds a depth level to your serialization with `left` or `right` value.\n\n```\nLeft(\"value\")  \u003c=\u003e { \"left\": \"value\" }\nRight(12345)   \u003c=\u003e { \"right\": 12345 }\n```\n\nYou will get a deserialization exception if both `left` and `right` are together.\n\n### Option\n\n```kotlin\nval data: Option\u003cString?\u003e = getDataFromSomewhere()\nval result: Option\u003cInt?\u003e = data.letSome { it?.toIntOrNull() }\nwhen (result) {\n    is Some -\u003e println(\"Success, result: ${result.value ?: \"'no value'\"}.\")\n    None -\u003e println(\"Nothing was found.\")\n}\n```\n\nThe `Option` class is also serializable with [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization).\n\nMake sure to not encode defaults in your encoder. Like in Json with `{ encodeDefaults = false }`. Then, when defining a\ndata class, initialize fields to `None`:\n\n```kotlin\n@Serializable\ndata class Data(val value: Option\u003cString?\u003e = None)\n```\n\nDoing it this way allows the deserializer to fall back to `None` when the field is not present inside a json object.\nIf the field is present and null, it deserializes to `Some(null)`.\nGiven the example above:\n\n```\nJSON                 \u003c=\u003e Kotlin\n{ }                  \u003c=\u003e Data(None) \n{ \"value\": null }    \u003c=\u003e Data(Some(null)) \n{ \"value\": \"value\" } \u003c=\u003e Data(Some(\"value\"))\n```\n\nYou can see the [test here](src/commonTest/kotlin/net/orandja/test/OptionJson.kt).\n\n# Api\n\n## Create\n\n```kotlin\nvar option: Option\u003cString\u003e\noption = Some(\"value\")\noption = None\n\nvar either: Either\u003cString, Int\u003e\neither = Left(\"value\")\neither = Right(1234)\n```\n\n## Side execution\n\n```kotlin\nval option: Option\u003cString\u003e\nval either: Either\u003cString, Int\u003e\n\nval _: Option\u003cString\u003e = option.alsoNone {}\nval _: Option\u003cString\u003e = option.alsoSome { str: String -\u003e }\nval _: Option\u003cString\u003e = option.alsoBoth(onNone = { }, onSome = { str -\u003e })\n\nval _: Either\u003cString, Int\u003e = either.alsoLeft { str: String -\u003e }\nval _: Either\u003cString, Int\u003e = either.alsoRight { int: Int -\u003e }\nval _: Either\u003cString, Int\u003e = either.alsoBoth(onLeft = { str -\u003e }, onRight = { int -\u003e })\n```\n\n## Transform\n\n### Either\n\n[//]: # (@formatter:off)\n```kotlin\nval either: Either\u003cString, Int\u003e = Left(\"value\")\n\nval _: String  = either.left  // Might throw an Exception\nval _: Int     = either.right // Might throw an Exception\nval _: String? = either.leftOrNull\nval _: Int?    = either.rightOrNull\n\nval _: Either\u003cInt, String\u003e  = either.invert() // invert types\n\nval _: Either\u003cUnit, Int\u003e    = either.letLeft { str: String -\u003e Unit }    // Transform left type\nval _: Either\u003cString, Unit\u003e = either.letRight { int: Int -\u003e Unit }      // Transform right type\nval _: Either\u003cUnit, Unit\u003e   = either.letBoth(onLeft = {}, onRight = {}) // Transform both types\n\nval a: String = either.foldRight { int: Int -\u003e \"new\" }     // Transform right value to left type\nval b: Int    = either.foldLeft { str: String -\u003e 0 }       // Transform left value to right type\nval _: Unit   = either.foldBoth(onLeft = {}, onRight = {}) // Transform both left and right value to same type\n\nassertTrue { a == \"value\" }\nassertTrue { b == 0 }\n```\n[//]: # (@formatter:on)\n\n### Option\n\n[//]: # (@formatter:off)\n```kotlin\nval option: Option\u003cString\u003e = Some(\"value\")\n\nval _: String  = option.value       // Might throw an Exception\nval _: String? = option.valueOrNull\n\nval _: Option\u003cUnit\u003e = option.letSome { str: String -\u003e Unit } // Transform value type\n\nval a: String = option.foldNone { \"new\" }                  // Create value type on None\nval _: Unit   = option.foldBoth(onNone = {}, onSome = {}) // Transform bot\nassertTrue { a == \"value\" }\n```\n[//]: # (@formatter:on)\n\n### Destructuring\n\n```kotlin\nval left = Left(\"value\")\nval right = Right(1234)\nval value = Some(Any())\n\nval (l: String) = left\nval (r: Int) = right\nval (v: Any) = value\n```\n\n### Either to Option\n\n```kotlin\nval either: Either\u003cString, Int\u003e = Left(\"value\")\n\nval a: Option\u003cString\u003e = either.leftAsOption()\nval b: Option\u003cInt\u003e = either.rightAsOption()\n\nassert(a is Some\u003cString\u003e)\nassert(b is None)\n```\n\n### Option to Either\n\n```kotlin\nval option: Option\u003cString\u003e = Some(\"value\")\n\nval _: Either\u003cString, Int\u003e = option.letNoneAsRight { 0 }\nval _: Either\u003cInt, String\u003e = option.letNoneAsLeft { 0 }\n\nval _: Either\u003cLong, Unit\u003e = option.letAsLeft(onSome = { 0L }, onNone = {})\nval _: Either\u003cUnit, Long\u003e = option.letAsRight(onSome = { 0L }, onNone = {})\n```\n\nIf you know the type of the Option, just create a left or right value with it. `val either = Left(option.value)`\n\n### Chaining\n\nYou can use the `try` function to chain calls and have a single error handle at the end.\n\n```kotlin\nobject Error\nfun String.toInt(): Either\u003cInt, ERROR\u003e = this.toIntOrNull()?.let { Left(it) } ?: Right(ERROR)\nfun Int.inBound(range: IntRange): Either\u003cInt, ERROR\u003e = if (this in range) Left(this) else Right(ERROR)\n\nval data: Either\u003cString, ERROR\u003e = Left(\"123\")\nval result: Int = data.tryLeft(String::toInt)\n    .tryLeft { it.inBound(0..\u003c100) }\n    .requireLeft { return }\n\nassertEquals(123, result)\n```\n\nYou can do the same thing with an `Option`\n\n```kotlin\nfun String.toInt(): Option\u003cInt\u003e = this.toIntOrNull()?.let { Some(it) } ?: None\nfun Int.inBound(range: IntRange): Option\u003cInt\u003e = if (this in range) Some(this) else none\n\nval data: Option\u003cString\u003e = Some(\"123\")\nval result: Int = data.trySome(String::toInt)\n    .tryLeft { it.inBound(0..\u003c100) }\n    .requireSome { return }\n\nassertEquals(123, result)\n```\n\n## Requiring values\n\nThere are no operators like `either.withLeft { }` or `option.withValue { }` as it implies some sort of error\nhandling if the type is wrong. Instead, the lib provides the inverted thinking, handles the error case and\ncontinues.\n\nIn a `require` block you are requiring to **return** or **throw** an exception.\nYou cannot let the code go at the end of the block.\n\nYou can use destructuring syntax to handle `Either` / `Option` values directly.\n\n#### Either\n\n```kotlin\nval either: Either\u003cString, Int\u003e = Left(\"value\")\n\nval _: String = either.requireLeft { value: Right\u003cInt\u003e -\u003e error(\"Will not fail\") }\nval _: Int = either.requireRight { value: Left\u003cString\u003e -\u003e error(\"Will fail\") }\n\nfun test() {\n    val _: String = either.requireLeft { (value: Int) -\u003e return@test }\n    val _: Int = either.requireRight { (value: String) -\u003e return@test }\n}\n```\n\n#### Option\n\n```kotlin\nval option: Option\u003cString\u003e = None\n\noption.requireNone { opt: Option\u003cString\u003e -\u003e error(\"Will not fail\") }\noption.requireNone { (value: String) -\u003e error(\"Will not fail\") }\n\nval _: String = option.requireSome { error(\"Will fail\") }\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl-briand%2Feither","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fl-briand%2Feither","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl-briand%2Feither/lists"}