dave08
05/14/2024, 10:49 AMcatch({ }) {}
but return a Result instead? It seems like runCatching is notorious for catching too much, but if I need to produce a Result in the end, then why not have something better to make it? (AFAIK Either is only when the Left isn't an exception... but if it is, it's preferrable to use Result?)Youssef Shoaib [MOD]
05/14/2024, 11:14 AMrunCatching { ... }.onFailure { it.nonFatalOrThrow() }
dave08
05/14/2024, 11:19 AMrunCatching
in catching a particular exception, whereas in catch({ }) { }
there is such a thing... that might also be a potential thing Arrow could add given it's philosophy.simon.vergauwen
05/14/2024, 12:14 PMsimon.vergauwen
05/14/2024, 12:15 PMresult { }
catches exceptions, just like runCatching
but it behaves the same as runCatching
.
Because we initially felt that breaking semantics with Kotlin Std would be confusing. However it seems more and more people are turning to catch, Either.catch as a solution for this but result { }
could also work great for that if we automatically take into account fatal errors.simon.vergauwen
05/14/2024, 12:15 PMCLOVIS
05/14/2024, 12:23 PMResult
? Either<Throwable, T>
is better in every way, and is less likely to be misused like Result
often isCLOVIS
05/14/2024, 12:23 PMsimon.vergauwen
05/14/2024, 12:36 PMsimon.vergauwen
05/14/2024, 12:36 PMCLOVIS
05/14/2024, 12:37 PMCLOVIS
05/14/2024, 12:37 PMResultInteroperabilityApi
opt-in?dave08
05/14/2024, 1:56 PMif you're using Arrow, why are you usingFirst, because internally Result doesn't wrap a success, it just puts it's value in an Any and casts it on get... it only wraps the exception which occurs less (hopefully...), also using?Result
catch
forces me to provide two lambdas (?), whereas runCatching just surrounds the computation and returns Result... (I could make a little function to adapt that, but that function would need to be copied/pasted everywhere I use this pattern...)CLOVIS
05/14/2024, 1:59 PMval foo = Either.catch {
// …your code…
}
First, because internally Result doesn't wrap a success, it just puts it's value in an Any and casts it on get... it only wraps the exception which occurs less (hopefully...)
Either
also only stores one single field for each case, I highly doubt there exists in a scenario in which one is measurably faster than the otherdave08
05/14/2024, 2:00 PMCLOVIS
05/14/2024, 2:00 PMRight
is a subclass of Either
, it's not 'wrapped'dave08
05/14/2024, 2:01 PMpublic value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
) : Serializable {
....
public companion object {
/**
* Returns an instance that encapsulates the given [value] as successful value.
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@InlineOnly
@JvmName("success")
public inline fun <T> success(value: T): Result<T> =
Result(value)
/**
* Returns an instance that encapsulates the given [Throwable] [exception] as failure.
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@InlineOnly
@JvmName("failure")
public inline fun <T> failure(exception: Throwable): Result<T> =
Result(createFailure(exception))
}
dave08
05/14/2024, 2:02 PMdave08
05/14/2024, 2:02 PMinternal fun createFailure(exception: Throwable): Any =
Result.Failure(exception)
`CLOVIS
05/14/2024, 2:03 PMCLOVIS
05/14/2024, 2:04 PMsimon.vergauwen
05/14/2024, 2:09 PMcatch
forces me to provide two lambdas (?)
Either.catch
works the same as runCatching
without two lambdas, right?simon.vergauwen
05/14/2024, 2:10 PMcatch
to return instead of taking two lambdas? 🤔CLOVIS
05/14/2024, 2:17 PMtake into account that Result can still box IIRCFor example,
List<Either<Throwable, T>>
is less memory-intensive than List<Result<T>>
, because Result
will box errors twice. But yeah, I highly doubt this makes any practical difference, especially because we rarely have long-lived Result or Either instancesdave08
05/14/2024, 2:42 PMWhat would you otherwise expectResult<T> 😉to return instead of taking two lambdas?catch
dave08
05/14/2024, 2:50 PMbecausevery good point... but I'm going on the outlook that most things will succeed when using Result... I would really consider using Either in cases that there are more common domain errors that could happen. And very rare major failures I would just let them throw... here I have a middle case that I need to save such not-common errors to a db record if they happen, but I'd rather save the overhead of that if possible. And in my case, I'm not using Result in List...will box errors twiceResult
dave08
05/14/2024, 3:06 PMCLOVIS
05/14/2024, 3:06 PMdave08
05/14/2024, 3:07 PMCLOVIS
05/14/2024, 3:07 PMCLOVIS
05/14/2024, 3:07 PMCLOVIS
05/14/2024, 3:07 PMCLOVIS
05/14/2024, 3:08 PMDelicateResultApi
opt-in, but oh wellCLOVIS
05/14/2024, 3:09 PMYoussef Shoaib [MOD]
05/14/2024, 3:13 PMdave08
05/14/2024, 3:16 PMResult
isn't just an Either<Throwable, T>
... apart from the runCatching
problem, it has mostly the same stuff... also instead of isLeft() there's a clear isFailure() since exceptions are mostly failures... whereas in domain errors, isLeft() might sometimes make more sense.CLOVIS
05/14/2024, 3:16 PMThrowable
bound that's baddave08
05/14/2024, 3:17 PMdave08
05/14/2024, 3:17 PMCLOVIS
05/14/2024, 3:18 PMEither<Throwable, T>
is not much better. The difference is Either
encourages you not to do that, whereas Result
forces you to do itYoussef Shoaib [MOD]
05/14/2024, 3:18 PMbehaves the same asresult
runCatching
Are you sure about that @simon.vergauwen? This line clearly calls
nonFatalOrThrow
so I'm pretty sure that when result { }
catches a fatal exception, it throws it immediately. Obviously, if someone explicitly raise(CancellationException)
, then that does get swallowed in the Result. Perhaps that's the semantic change you're aiming to make?dave08
05/14/2024, 3:18 PMResult.catch { }
function like Either.catch { }
dave08
05/14/2024, 3:19 PMCLOVIS
05/14/2024, 3:19 PMCLOVIS
05/14/2024, 3:20 PMCLOVIS
05/14/2024, 3:21 PMdave08
05/14/2024, 3:21 PMCLOVIS
05/14/2024, 3:21 PMCLOVIS
05/14/2024, 3:22 PMCLOVIS
05/14/2024, 3:23 PMYoussef Shoaib [MOD]
05/14/2024, 3:23 PMResult
usage by changing how result { }
works to rethrow fatal exceptions.dave08
05/14/2024, 3:24 PMI think Arrow could instead put a bandaid onAnd also addingusage by changing howResult
works to rethrow fatal exceptions.result { }
fun Result.catch(...
could help in that bandaid...dave08
05/14/2024, 3:25 PMbut Arrow should also make it as clear as possible to users that it's only there for interop and not recommended in generalas long as there's interop, there'll be tripups... so why not help users and at the same time write what you're pointing out in the interop kdocs?
CLOVIS
05/14/2024, 3:26 PMCLOVIS
05/14/2024, 3:27 PMdave08
05/14/2024, 3:28 PMtypealias Result<T> = Either<Throwable, T>
and use that... 😉. I always read KDocs, and there could always be a deprecation with a replace with...CLOVIS
05/14/2024, 3:28 PMThrowable
as a failure bound 😅dave08
05/14/2024, 3:29 PMtypealias Result<T> = Either<Exception, T>
CLOVIS
05/14/2024, 3:30 PMCLOVIS
05/14/2024, 3:30 PMCLOVIS
05/14/2024, 3:30 PMdave08
05/14/2024, 3:31 PMdave08
05/14/2024, 3:31 PMCLOVIS
05/14/2024, 3:32 PMYoussef Shoaib [MOD]
05/14/2024, 3:33 PMResult.catch
is not really necessary because result { }
does the same and also allows bind()
(note that in a Raise-less Result.catch
, getOrThrow
is effectively bind
)CLOVIS
05/14/2024, 3:34 PMwhat can I do that KotlinX Serialization (my current use case) throws exceptions?Catch the serialization exception specifically (without using Either.catch or anything else), wrap it into a custom class like
SerializationFailure(val initial: WhateverTheKotlinXSerializationParentExceptionIs)
and use Either<SerializationFailure, T>
CLOVIS
05/14/2024, 3:35 PMdave08
05/14/2024, 3:40 PMThrows:
SerializationException - if the given JSON string is not a valid JSON input for the type T
IllegalArgumentException - if the decoded input cannot be represented as a valid instance of type T
dave08
05/14/2024, 3:41 PMdave08
05/14/2024, 3:41 PMCLOVIS
05/14/2024, 3:42 PMCLOVIS
05/14/2024, 3:42 PMdave08
05/14/2024, 3:43 PMdave08
05/14/2024, 3:44 PMdave08
05/14/2024, 3:51 PMEither.catch<Exception> { }
? Isn't there a variant like that in the regular top-level catch
? There's only on Throwable
...dave08
05/14/2024, 3:53 PMdave08
05/14/2024, 3:54 PMcatch
...dave08
05/14/2024, 3:55 PMpublic inline fun <reified T : Throwable, A> catch(block: () -> A, catch: (t: T) -> A): A =
catch(block) { t: Throwable -> if (t is T) catch(t) else throw t }
dave08
05/14/2024, 3:55 PMpublic inline fun <reified T : Throwable, R> catchOrThrow(f: () -> R): Either<T, R> =
arrow.core.raise.catch<T, Either<T, R>>({ f().right() }) { it.left() }
simon.vergauwen
05/14/2024, 4:10 PMEither.catchOrThrow
is a resolution issue IIRC. It cannot be defined as catch
dave08
05/15/2024, 9:19 AMsimon.vergauwen
05/15/2024, 9:56 AMsimon.vergauwen
05/15/2024, 9:57 AMdave08
05/15/2024, 10:01 AMdave08
05/15/2024, 10:03 AMsimon.vergauwen
05/15/2024, 10:41 AMphldavies
05/15/2024, 5:09 PMcatch
being catch(block, catch)
and not catch(catch, block)
? I keep wanting to do catch({ it: Throwable -> handleOrRaise(it) }) { doMyActualLogic() }
phldavies
05/15/2024, 5:10 PMwithError
is structured)simon.vergauwen
05/15/2024, 5:11 PMEffect.catch
to this 😅simon.vergauwen
05/15/2024, 5:12 PMphldavies
05/15/2024, 5:13 PMcatch
function name to the "block" doing the catching - ala try/catch (although in this case the "try" code is encapsulated in the following/trailing block)CLOVIS
05/15/2024, 5:13 PMwithCatching
🫣simon.vergauwen
05/15/2024, 5:14 PMsimon.vergauwen
05/15/2024, 5:15 PMEither.catch(catch, block)
existed before 1.0...phldavies
05/15/2024, 5:15 PMinline fun <A> catching(catch: (throwable: Throwable) -> A, block: () -> A): A = catch(block, catch)
to help my brain out at times 😄CLOVIS
05/15/2024, 5:16 PMsimon.vergauwen
05/15/2024, 5:20 PMsimon.vergauwen
05/15/2024, 5:21 PMCLOVIS
05/15/2024, 5:21 PMCLOVIS
05/15/2024, 5:22 PMsimon.vergauwen
05/15/2024, 5:22 PMsimon.vergauwen
05/15/2024, 5:24 PMwithError
, etc. So I would in favor of banning failures, and moving to "typed error" or "error" for consistency. WDYT?
(Sorry to hijack your thread @phldavies)CLOVIS
05/15/2024, 5:27 PMCLOVIS
05/15/2024, 5:27 PMsimon.vergauwen
05/15/2024, 5:28 PMError
, but it'll be used in docs (preferable typed error), and in (very very few) operators like withError
. So I think it's okay, I agree ideally it would not conflict with anything kotlin.*
simon.vergauwen
05/15/2024, 5:30 PMwithError
😅 And just for that only, I'd take the hit and go with Error instead of Failure. Just to share my rationale for this.CLOVIS
05/15/2024, 5:35 PMCLOVIS
05/15/2024, 5:35 PMCLOVIS
05/15/2024, 5:35 PMsimon.vergauwen
05/15/2024, 5:37 PMIt's so new though XDMaintainer PTSD?
CLOVIS
05/15/2024, 5:39 PMsimon.vergauwen
05/15/2024, 5:50 PMsimon.vergauwen
05/15/2024, 5:52 PM@ExperimentalRaise
annotation.CLOVIS
05/15/2024, 5:58 PMCLOVIS
05/15/2024, 5:59 PMsimon.vergauwen
05/15/2024, 5:59 PMCLOVIS
05/15/2024, 6:00 PM::toDto
everywhereYoussef Shoaib [MOD]
05/15/2024, 6:01 PMError
convention there stemmed from me seeing Error
as the type param name everywhere in Arrow's codebase. I think changing the name now would be annoying, and I think the idea of a "typed Error" is definitely a good mental model for Raise.CLOVIS
05/15/2024, 6:01 PMeither {
...
...
}.mapLeft(::toDto)
.bind()
which is just... urh 😓