do
a <- somePartialResult
b <- partialFunction1 a
c <- partialFunction2 a b
return c
where we assume signatures like somePartialResult : Either<A, Error>
partialFunction1 : A -> Either<B, Error>
partialFunction2 : A -> B -> Either<C, Error>
this overall computation has a signature Either<C, Error>. The way it works is that the first failing computation (the first Either that's actually Left-y) will short-circuit and become the final result value. Only if all of the partial computations succeed (are Right-y) will the final result by Right(c).In Haskell we don't have an early return syntax like `return` and function scope. Instead, we construct something equivalent using `do` syntax. This can be a little weightier than `return`, but the upside is that you can construct other variants of things like early returns that can be more flexible.
Or, should we resort to a method of `Either` that transforms its `Left` in that case?
Rust offers a similar facility (though specialized to just handle a couple kinds of error handling) using its `?` syntax. This works essentially identically to the do syntax above, but also includes a call to transform whatever error type is provided into the error type of the function return.
Note that in Rust (a) this technique only, today, works at function boundaries and (b) will always be explicitly annotated since all functions require an explicit type. This helps a bit over Haskell's more general approach as it provides some additional data to help type inference along.
That said, if you were interested, it's likely possible to emulate something very similar to Rust's technique in Haskell, too.
But I don't think I've ever seen that. It just doesn't feel as stylish in Haskell. The From/Into traits define a behavior that's much more pervasive than most type classes in Haskell. It works well for Rust, but is I think less compelling to the Haskell community.
https://typelevel.org/cats/datatypes/eithert.html
def divisionProgramAsync(inputA: String, inputB: String): EitherT[Future, String, Double] =
for {
a <- EitherT(parseDoubleAsync(inputA))
b <- EitherT(parseDoubleAsync(inputB))
result <- EitherT(divideAsync(a, b))
} yield result def divisionProgramAsync(inputA: String, inputB: String): IO[String, Double] =
for {
a <- parseDoubleAsync(inputA)
b <- parseDoubleAsync(inputB)
result <- divideAsync(a, b)
} yield result
(the annoying wrapping/unwrapping isn't necessary with ZIO here)You can also write this shorter if you want:
def divisionProgramAsync(inputA: String, inputB: String): IO[String, Double] =
for {
(a, b) <- parseDoubleAsync(inputA) <*> parseDoubleAsync(inputB)
result <- divideAsync(a, b)
} yield result result <- do
a <- someEitherValue
b <- anotherEitherValue
return (doStuff a b)
In the above example the do notation will unwrap the values as an and b, but if one of the results is Left, the computation is aborted, returning the Left value.This is one just of the many techniques available to make error checking linear.
I was going through a Scala codebase at work that uses `Future`s and `map`ing and `flatMap`ing them. Sometimes the callbacks went 5-6 levels deep. Is there a way to "linearlize" such code?
I come from JS/TS background and have not much experience with pufe functional languages. But I love how TS handles discriminated unions - if we handle a branch and `return` early, that branch is removed from the union for the subsequent scope, and I was wondering if something of that sort can be achieved in Haskell/Scala.