I'm struggling to understand Arrow's Either monad....
# arrow
d
I'm struggling to understand Arrow's Either monad. I tried implementing a function that parses a String and returns an Int (or throws a
ParseError
if the string could not be parsed for some reason). The function has the following properties • the input string can be an
Int
or a
Double
. If the value can be parsed (happy path), a
Either.Right<Int>
is returned • when passing a
Double
, it will be ceiled to the next integer value (so
"42.1"
will be parsed as
43
) • when passing a
Double
, both
.
and
,
are accepted as decimal separators. If this condition is not met, a
Either.Left<ParseError.InvalidDecimalPoint>
is returned.
--> omitted for the sake of a simpler example • When passing a
Double
, the value must be within in the Interval
[Int.MIN_VALUE, Int.MAX_VALUE]
. If this condition is not met, a
Either.Left<ParseError.ValueTooLow>
resp. a
Either.Left<ParseError.ValueTooHigh>
is returned • If the string could not be parsed for some other reason (e.g. because it contains non-numeric characters), a
Either.Left<ParseError.NotANumber>
is returned. Based on the above constraints, the function's signature is
String -> Either<ParseError, Int>
. I came up with the solution from the code snippet below, but I can't find a way to chain the eithers together. There's also some code duplication. Seems like there's a
flatMapLeft
function missing. Or am I missing something? What's the most elegant, Kotlin-idiomatic way to implement this?
s
you can use
recover
-> https://apidocs.arrow-kt.io/arrow-core/arrow.core/recover.html when chaining these eithers
Copy code
intEither.recover { doubleEither }.recover { decimalPointEither }
d
That does not compile. I'm importing
import arrow.core.recover
a
Raise DSL, use the
either
builder and
catch
to transform exceptions to typed errors, see here for a comprehensive example: https://blog.rockthejvm.com/functional-error-handling-in-kotlin-part-3/ edit: in fact, you probably need to use nested
catch
if you want this in a single function. It’s perhaps simpler to split the ”parsers” and then chain them separately, likely with
recover
❤️ 1
y
I think something is slightly wrong with your spec because I can't seem to figure out how to differentiate between the case where I should raise
InvalidDecimalPoint
and when I should raise
NotANumber
. The code here thus doesn't use
NotANumber
at all. Regardless, here's how I would do it:
If you don' want to use contexts, that's fine! You can readapt the code to use
either
and
Either
types with
.bind()
calls when necessary. I personally think the code in
toIntBasedOnSpec
is idiomatic Kotlin with Arrow.
d
@Youssef Shoaib [MOD]: You're right, there's no way of differentiating between an
InvalidDecimalPoint
and
NotANumber
. I omitted it in my latest implementation. I tried to get it down to a single function without contexts (because context receivers show up as experimental and I don't want to use experimental features) and without Nullability (because I don't like the Elvis operator). So far I got it down to this function, which nests the eithers using pattern matching in a
when
clause. However, I was hoping there would be a more readable/fluent way... Btw: I now pushed the code to my Repo, where there's also an accompanying test suite to check the implementation: https://github.com/tiefenauer/mp-techlunch-kotlin-arrow/blob/main/src/main/kotlin/NumberParseMonad.kt
y
Expect a PR soon enough from me! But just FYI, the elvis operator is the idiomatic way to express "recovery", and so any solution not using it will be more clunky
🙏 1