Hi I have a question regarding combining 2 Monads....
# arrow
m
Hi I have a question regarding combining 2 Monads. I have the following type
Either<A, Option<B>
and want to create a
Either<A, B>
. If the Option is
None
the Either should be
Left<A>
and if it is
Some
I want the Either to be
Right<B>
. I did not find a function in the library and since MonadeTransformers are not an option I came up with the following solution:
Copy code
fun <A, B> Either<A, Option<B>>.unwrapOptionOrElse(other: () -> A): Either<A, B> {
    return flatMap { option -> option.fold({ other().left() }, { it.right() }) }
}
Is this a reasonable way of handling this case or should I change something? Regards Moritz 🙂
r
Hi @Moritz Lindner, that looks good, but ending up in a place where you have a
Either<A, Option<B>>
in application code is often times a sign there is too much indirection and wrapping that could potentially be resolved in a different way. do you have an example on where this function is useful ?
👍 1
m
Hi @raulraja thx for the fast response 🙂 Our persistence layer is currently returning
Either<A, Option<B>
for a typical
findById
Function. We choose this approach because we currently don't use a restricted hierarchy for the left case. (We basically have just something like this:
Copy code
class Error(
    val errorMessage: String,
    val errorCode: ErrorCode,
    val httpStatusCode: Int,
    val errorUid: UUID,
    throwable: Throwable? = null
) {
/// ...
}
We could use this
Error
to describe the typical "No Element by this id" but we choose to encode this as a
Option<B>
Right
case.
So to complete the sample here is a typical callside:
Copy code
private fun getBetriebsstelle(command: CreateStoerungsmeldungCommand): Either<Error, Betriebsstelle> {
    return loadBetriebsstelle.loadByRil100(command.stoerungsOrtId)
        .unwrapOptionOrElse { buildErrorFromErrorCode(KodiErrorCode.API_2002) }
}
We want to "unwrape" to Option to use this
getBetriebsstelle
function in an either forcomprehension:
Copy code
return either.eager {
///...
            val netzkode = getNetzKode(createStoerungsmeldungCommand).bind()
            val betriebsstelle = getBetriebsstelle(createStoerungsmeldungCommand).bind()

            val kodiErrorCode = checkIfStoerungsmeldungIsValid(createStoerungsmeldungCommand)

///...
        }
I hope this is somewhat understandable 🙂
r
I’m familiar with english and spanish and mostly latin langs, what do those terms mean?
m
Haha it's German :D
😅 2
We would love to use English but frankly it isn't really possible to have a perfect 1 to 1 mapping :D and it easier when talking with our stackholders :D
r
Makes sense, was just trying to understand the context around what it means
getBetriebsstelle
and how
unwrapOptionOrElse
plays a role there. For what I see in the structure of the for comprehension there may be a chance to express this function differently if you can use context receivers. @Imran/Malic also speak german natively and may be able to help better than I can.
I once wrote a game in basic in a foreign lang and thought it made sense that people that invented and used it did it in their lang for max expression. Maybe in the future we don’t have to settle for just english and still can share with everyone around regardless of the actual code. 🤞
i
Ich glaub was vielleicht hilft ist:
Copy code
either.eager { 
  ///...
            val netzkode = getNetzKode(createStoerungsmeldungCommand).bind()
            val betriebsstelle = getBetriebsstelle(createStoerungsmeldungCommand).bind()

            // val kodiErrorCode = checkIfStoerungsmeldungIsValid(createStoerungsmeldungCommand) // anstatt checkifStoerungsmeldungIsValid benutzt du ensureNotNull/ ensure von EagerEffectScope
    /// .....
}
du kannst dann entweder checkifStoerungsmeldungIsValid so definieren (??? ist für Typen die ich von dem Kontext oben nicht rauslesen kann):
Copy code
context(EagerEffectScope<Error>)
fun checkifStoerungsmeldungIsValid(createStoerungsmeldungCommand : ???): KodiErrorCode { 
   ensureNotNull(//** **//) { buildErrorFromErrorCode(KodiErrorCode.API_2002) }
   // oder wenn es um Boolean Expressions geht kannst du `ensure` nutzen
  // ...
  return kodiErrorCode
}
Der Trick ist einfach den ErrorCode im lambda von ensure oder ensureNotNull zu passen, damit spart man sich das extra wrapping. Und falls du in der Domain mit Null arbeiten musst oder sollst dann kann man auch mit Option.fromNullable. Falls du noch fragen hast sag bescheid 😄
r
I translated that as best as I could and I got something about “saving extra wrapping”, that must be good XD, thanks @Imran/Malic
😎 1
i
Correct, I was just mentioning that when the validation step happens within the checkIfStoerungsmeldungIsValid, they could pass the Errortype in ensureNotNull or ensure thus
shift
will propagate the Error value and there is no need for Option
m
Hi thx for your help @Imran/Malic 🙂 I`ll try it today and will let you know what happend!
👌🏾 1
Sorry for the late response but unforeseen events occurred 😄 In the end we decided to model it as
Either<A, B>
and made the Not Found Case part of the left. Therefore my whole question was unimportant in the end... But I am still glad that I because I still learned some things 🙂 So a big thx to you tow
🙌 1
🙌🏾 1
i
Gerne Moritz 😊
🙂 1