Last active
April 7, 2025 19:35
-
-
Save edreyer/b7fc887bb2ac0b8f1b7234a4a460afd9 to your computer and use it in GitHub Desktop.
Preview of context parameters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.bridge | |
import arrow.core.Either | |
import arrow.core.raise.Raise | |
import arrow.core.raise.ensure | |
import arrow.core.raise.ensureNotNull | |
// Functions useful for bridging from context receivers to the new context parameters | |
// This is temporary and will not be required (or provided by library authors) in the future | |
context(raise: Raise<Err>) | |
inline fun <Err> raise(r: Err): Nothing = raise.raise(r) | |
context(raise: Raise<Err>) | |
inline fun <Err> ensure(condition: Boolean, r: () -> Err): Unit = raise.ensure(condition, r) | |
context(raise: Raise<Err>) | |
inline fun <A, Err> ensureNotNull(value: A?, r: () -> Err): A = raise.ensureNotNull(value, r) | |
context(r: Raise<Err>) | |
inline fun <Err, A> Either<Err, A>.bind(): A = with(r) { bind() } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example | |
sealed interface ExecutionError | |
data class ErrorParent(val message: String) : ExecutionError | |
data class ErrorChild(val message: String) : ExecutionError |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.contextparams | |
import arrow.core.Either | |
import arrow.core.raise.Raise | |
import arrow.core.raise.either | |
import com.example.ErrorChild | |
import com.example.ErrorParent | |
import com.example.ExecutionError | |
import com.example.bridge.ensure | |
import com.example.justeither.BusinessLogic | |
// Using the new context parameters feature in Kotlin 2.1.20 | |
// This shows how you can do functional error handling in a way that has | |
// semantics similar to throwing exceptions but differs in the following ways: | |
// * errors are always "returned". That is, they are part of the method signature by virtue | |
// of the compile time enforced Raise context which must be provided | |
// * But even though errors are "returned" from all functions, you only have to deal with them at the point | |
// where the Raise context is provided. This would be similar to where a try..catch block would be | |
// * this mechanism frees you from having deeply nested calls from having every level in the stack | |
// to deal with the error. You can just "raise" the error and it will bubble up to the caller | |
// * One advantage of this is that Errors don't have to be exceptions. They can be any type. Typically | |
// this would be an ADT structure with a sealed interface and data classes for each error type. | |
// * Another advantage is that this approach is that you write happy path code, but the typesafe error | |
// handling is baked in and enforced at compile time. This provides the same "convenience" of using | |
// exceptions but is much more typesafe and forces the engineer to consider failure modes while happy | |
// path code is being written. THis is in contrast to exceptions which are often an afterthought. | |
fun main() { | |
val result: Either<ExecutionError, Int> = either { BusinessLogic.doWorkParent(12) } | |
result | |
.onLeft { println(it) } | |
.onRight { println(it) } | |
val result2: Either<ExecutionError, Int> = either { BusinessLogic.doWorkParent(8) } | |
result2 | |
.onLeft { println(it) } | |
.onRight { println(it) } | |
} | |
object BusinessLogic { | |
context(_: Raise<ExecutionError>) | |
fun doWorkParent(n: Int): Int { | |
val childResult: Int = doWorkChild(n) | |
ensure (childResult % 2 == 0) { ErrorParent("n is not divisible by 2") } | |
return n | |
} | |
context(_: Raise<ExecutionError>) | |
fun doWorkChild(n: Int): Int { | |
val grandChildResult = doEvenMoreWorkChild(n) | |
ensure (grandChildResult % 4 == 0) { ErrorChild("n is not divisible by 4") } | |
return grandChildResult | |
} | |
context(_: Raise<ExecutionError>) | |
fun doEvenMoreWorkChild(n: Int): Int { | |
ensure (n % 3 == 0) { ErrorChild("n is not divisible by 3") } | |
return n | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.justeither | |
import arrow.core.Either | |
import arrow.core.raise.either | |
import arrow.core.raise.ensure | |
import com.example.ExecutionError | |
import com.example.ErrorParent | |
import com.example.ErrorChild | |
// Showing typed error handling with plain ol' Either plus the Either DSL | |
fun main() { | |
val result: Either<ExecutionError, Int> = BusinessLogic.doWorkParent(12) | |
result | |
.onLeft { println(it) } | |
.onRight { println(it) } | |
val result2: Either<ExecutionError, Int> = BusinessLogic.doWorkParent(8) | |
result2 | |
.onLeft { println(it) } | |
.onRight { println(it) } | |
} | |
object BusinessLogic { | |
fun doWorkParent(n: Int): Either<ExecutionError, Int> = either { | |
val childResult = doWorkChild(n).bind() | |
ensure(childResult % 2 == 0) { ErrorParent("n is not divisible by 2") } | |
childResult | |
} | |
fun doWorkChild(n: Int): Either<ExecutionError, Int> = either { | |
val grandChildResult = doEvenMoreWorkChild(n).bind() | |
ensure (grandChildResult % 4 == 0) { ErrorChild("n is not divisible by 4") } | |
grandChildResult | |
} | |
fun doEvenMoreWorkChild(n: Int): Either<ExecutionError, Int> = either { | |
ensure (n % 3 == 0) { ErrorChild("n is not divisible by 3") } | |
n | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment