MarioAriasC / funKTionale

Functional constructs for Kotlin
915 stars 71 forks source link

Either carrying like Rust #34

Open Redrield opened 6 years ago

Redrield commented 6 years ago

In Rust, the stdlib provides an Either like enum called Result. Functions that return Result can also carry values from other Result returning functions up the call stack. So if you're performing disk IO, where you'll be getting Result types as returns based off of whether there was an error opening a file, writing to disk etc. All this can be represented by a macro, or an operator.

fn i_return_a_result() -> Result<String, std::io::Error> {
    let i_returned_a_result: File = file_operation_that_returns_result()?;
    try!(write_to_file(i_returned_a_result));
    Ok("We did it!".to_string())
}

To those not familiar with Rust, let me explain. The function declaration is similar to one you would find in Haskell or using FunKTionale using Either, with the difference being that the left side is the result, and the right side is the error, exception, or what have you. In the function body, there were 2 instances where I would have needed to deal with checking that there was a proper value in the result. When I get the file, and when I write (I didn't bind a variable in that case, because it's often used that functions return a Result with an empty left side, just indicating success). In both cases, were there an error at any point¸ my function would have stopped executing, and the Error from their result would propogate up as the return value of my function, allowing it to be dealt with at the top.

Would this be at all possible in funcKTionale? Taking a Either returning function and collapsing it down to either give you the value of Right, or return from your function with the value of left?

okkero commented 6 years ago

This should work. But there may exist a better solution that doesn't use exceptions to make early returns.

fun <Err, T> eitherDo(block: EitherDo.() -> Either<Err, T>): Either<Err, T> =
        try {
            EitherDo.block()
        } catch (earlyReturn: EitherEarlyReturn) {
            Either.Left(earlyReturn.err as Err)
        }

object EitherDo {

    fun <Err, T> unwrap(either: Either<Err, T>): T {
        when (either) {
            is Either.Left -> throw EitherEarlyReturn(either.l)
            is Either.Right -> return either.r
        }
    }

}

//Stupidly, Throwable subclasses cannot be generic
private class EitherEarlyReturn(val err: Any?) : Throwable()
raulraja commented 6 years ago

@MarioArias I think what @Redrield is asking is for monad comprehensions for the different monadic types that there are in funKTionale, Either, Option, Disjunction, etc. Those can be easily supported with coroutines in the same way we do in Kategory. https://github.com/kategory/kategory/blob/a197446fd2ee2e777f7ca5cfa44077801bf1328d/kategory-core/src/main/kotlin/kategory/typeclasses/MonadContinuations.kt#L118 Would funKTionale embrace `for comprehensions in this style?

val x : Either<ErrorType, Result> = TODO()
val y : Either<ErrorType, Result> = TODO()
val z : Either<ErrorType, Result> = TODO()

Either.binding {
  val a: Result = x.bind() //suspended function
  val b: Result = y.bind() //suspended function
  val c: Result = z.bind() //suspended function
  yields(listOf(a, b, c))
}
// results in Either[Error, List[Result]]
val y : Either<ErrorType, Result> = Left(IWillShortcircuit)

Either.binding {
  val a: Result = x.bind() //suspended function
  val b: Result = y.bind() //suspended function but left is found so no further flatMaps and shortcircuits
  val c: Result = z.bind() //suspended function
  yields(listOf(a, b, c))
}
// results in Either[Error, List[Result]] with a `Left(IWillShortcircuit)` value.

If the answer is yes. I'd like to help build it. I'm interested in Kategory and funKTionale being good brothers and share common styles wherever it makes sense for the good of the Kotlin FP community. :beers: