CLOVIS
04/23/2024, 3:52 PMEither variant that supports unfinished operations using a three-state sealed class. How could interoperability with the Raise DSL look like?simon.vergauwen
04/23/2024, 3:54 PMsimon.vergauwen
04/23/2024, 3:56 PMRaise per failure subcase.
inline fun <E, A> outcome(block: (Raise<Incomplete>, Raise<Failure<E>>) () -> A): ProgressiveOutcome<E, A> = ...simon.vergauwen
04/23/2024, 3:56 PMIncomplete and Failure and put that into Raise<E>Youssef Shoaib [MOD]
04/23/2024, 3:57 PMRaise<Error>, and a Raise<Progress> with contexts
Dang, Simon beat me to it, although I'm suggesting to make sure the type is unwrappedCLOVIS
04/23/2024, 3:57 PMCLOVIS
04/23/2024, 3:58 PMProgressiveOutcome.Success can be in-progresssimon.vergauwen
04/23/2024, 3:58 PMI see, so the Quiver team considers that any non-finished value is a raise-able failureUhm, no 🤔 I wrote that implementation, but did a dirty trick to allow naturally binding
Either<E, A>. It's a pretty neat trick 😄
Allow raising Any?, and have the public API nicely typed.simon.vergauwen
04/23/2024, 3:59 PMCLOVIS
04/23/2024, 3:59 PMCLOVIS
04/23/2024, 3:59 PMsimon.vergauwen
04/23/2024, 4:05 PMsimon.vergauwen
04/23/2024, 4:05 PMEither<E, A>.bind() on Left should now show-up as ProgressiveOutcome.Failure which is pretty coolYoussef Shoaib [MOD]
04/23/2024, 4:12 PMFailure. The code also doesn't handle Incomplete on the recover side which I'm guessing was a simple oversight, but it kinda shows how easily the code can do unexpected things.
Perhaps what would be better is 3 context receivers:
Raise<Progress> for incomplete, Raise<Pair<E, Progress>> for Failure, and a Raise<E>, which is just the failure raise .withError({ it to done() }) { ... }simon.vergauwen
04/23/2024, 4:16 PMsimon.vergauwen
04/23/2024, 4:35 PMprogress state to OutcomeRaise.
val progress: Int
fun updateProgress(block: (Int) -> Int): Unitsimon.vergauwen
04/23/2024, 4:36 PMdone by default. Some strategy can even be applied here, which is accepted as param in outcome { }.simon.vergauwen
04/23/2024, 4:37 PMI can see issues here if e.g. someone tries to raise aCan you give an example?. The code also doesn't handleFailureon the recover side which I'm guessing was a simple oversight, but it kinda shows how easily the code can do unexpected things.Incomplete
Youssef Shoaib [MOD]
04/23/2024, 4:43 PMIncomplete(42).bind() results in a Failure(Incomplete(42), Int.MAX_VALUE)
Secondly, with a OutcomeRaise<Outcome<F, S>>, calling raise(myFailure) where myFailure: Failure<F> will fail with a CCE when later examined because it'll "flatten out" the failure i.e. it'll result in myFailure when the intention was Failure(myFailure, Int.MAX_VALUE)simon.vergauwen
04/23/2024, 4:44 PMCLOVIS
04/23/2024, 4:45 PMCLOVIS
04/23/2024, 4:47 PMraise(5) 🤔Youssef Shoaib [MOD]
04/23/2024, 4:48 PMraise(myFailure) can be fixed without a bunch of code gymnastics. In fact, now I realise that this is an issue with Quiver too. Inside of an OutcomeRaise<Outcome<...>>, doing raise(Absent) will result in an output of Absent instead of Failure(Absent)simon.vergauwen
04/23/2024, 4:48 PMsimon.vergauwen
04/23/2024, 4:48 PMsimon.vergauwen
04/23/2024, 4:48 PMsimon.vergauwen
04/23/2024, 4:48 PMfun main() {
val x = outcome<String, Int> {
ProgressiveOutcome.Success(1).bind() + ProgressiveOutcome.Success(1).bind()
// ProgressiveOutcome.Incomplete(41).bind()
// "Failure".left().bind()
ProgressiveOutcome.Failure("Failure").bind()
}
println(x)
}
This all works as expectedsimon.vergauwen
04/23/2024, 4:49 PMraise("Failure")simon.vergauwen
04/23/2024, 4:49 PMFailure, but Incomplete works fine for me.simon.vergauwen
04/23/2024, 4:50 PMsimon.vergauwen
04/23/2024, 4:50 PMraise(ProgressiveOutcome.Failure("Failure")) rather than bind, right?Youssef Shoaib [MOD]
04/23/2024, 4:50 PMCLOVIS
04/23/2024, 4:50 PMraise overloadsimon.vergauwen
04/23/2024, 4:51 PMRaise is fixed to E, not ProgressiveOutcomesimon.vergauwen
04/23/2024, 4:51 PMsimon.vergauwen
04/23/2024, 4:51 PMfailure(progress: Progress, e: E)simon.vergauwen
04/23/2024, 4:52 PMRaise<E>.() -> A based program inside your DSLYoussef Shoaib [MOD]
04/23/2024, 4:59 PMfun main() {
fun consumeProgressiveOutcome(outcome: ProgressiveOutcome<String, Unit>) { }
val x = outcome<ProgressiveOutcome<String, Unit>, Int> {
raise(ProgressiveOutcome.Failure("error"))
}
when (x) {
is ProgressiveOutcome.Failure -> {
consumeProgressiveOutcome(x.failure)
}
else -> {}
}
}
The way around this would be to use custom wrapper types inside OutcomeRaise, and then unwrap inside outcome, while insuring that those custom wrappers never escape to the outside world. The issue here is that we use Failure and Incomplete as signalling values internally, so sometimes we might mistaken legitimate values as signalsCLOVIS
04/23/2024, 5:01 PMRaise<E>.() -> A , isn't this simpler?
@JvmInline
value class ProgressiveOutcomeDsl<Failure>(private val raise: Raise<ProgressiveOutcome.Unsuccessful<Failure>>) : Raise<Failure> {
override fun raise(r: Failure): Nothing =
raise(r, done())
@JsName("raiseUnsuccessful")
fun raise(failure: ProgressiveOutcome.Unsuccessful<Failure>): Nothing =
raise.raise(failure)
@JsName("raiseWithProgress")
fun raise(failure: Failure, progress: Progress = done()): Nothing =
raise(ProgressiveOutcome.Failure(failure, progress))
// …bind and stuff
}simon.vergauwen
04/23/2024, 5:02 PMsimon.vergauwen
04/23/2024, 5:03 PMYoussef Shoaib [MOD]
04/23/2024, 5:05 PMCLOVIS
04/23/2024, 5:06 PMYoussef Shoaib [MOD]
04/23/2024, 5:14 PMRaise functions. E.g. if someone calls recover and inside calls raise(failure, progress), the raised value won't be caught by recover. If you're going all-in on contexts, then I'd suggest instead using context(Raise<ProgressiveOutcome.Unsuccessful<Failure>>, Raise<Failure>), and then exposing your raiseUnsuccessful and raiseWithProgress as extension funs. This way, you get support for recover et al. You wouldn't even need your own value class thenCLOVIS
04/23/2024, 5:16 PMEverything is wrapped in Failure, but looking at it, it's actually not unnecessary.Is it not? The return type of the DSL is
ProgressiveOutcome<Failure, Value> anyway, so the wrapping will happen by that point anyway, no?CLOVIS
04/23/2024, 5:17 PMIf you're going all-in on contexts,…I am, but I support multiple platforms, so although I can start to design with them in my mind, I can't use them 😕
CLOVIS
04/23/2024, 5:17 PME.g. if someone callsI don't understand this situation, do you have an example?and inside callsrecover, the raised value won't be caught by recover.raise(failure, progress)
Youssef Shoaib [MOD]
04/23/2024, 5:18 PMwithError calls in a contexts worldYoussef Shoaib [MOD]
04/23/2024, 5:21 PMoutcome<String, Int> {
recover({
raise("String", Progress.blah(42))
}) { 1 }
}
This should return Success(1) ideally, but here I think it either results in a compiler error because of unspecified type params, or it results in Failure("String", Progress.blah(42)).
However, if `outcome`'s block has context(Raise<Failure>, Raise<ProgressiveOutcome.Unsuccessful<Failure>>) and your functions are defined as extensions on Raise<ProgressiveOutcome.Unsuccessful<Failure>>, then everything works as expected and you get 1.simon.vergauwen
04/23/2024, 5:23 PMIor, etcYoussef Shoaib [MOD]
04/23/2024, 5:27 PMfold or any other Raise builders, the issue reappears. The real solution (at least as far as I've prototyped) is to do away with the wrapper Raise classes and instead use contexts. For instance, IorRaise<E> can be decomposed into context(Raise<E>, IorAccumulate<E>) where IorAccumulate deals with the accumulation logic, and Raise does its thing. With such decomposition, everything works as expected because recover simply replaces the Raise part without touching the IorAccumulate.CLOVIS
04/23/2024, 5:28 PM@RaiseDsl stop that from happening? It's a @DslMarker, right?simon.vergauwen
04/23/2024, 5:28 PMsimon.vergauwen
04/23/2024, 5:29 PMCLOVIS
04/23/2024, 5:29 PMsimon.vergauwen
04/23/2024, 5:30 PMCLOVIS
04/23/2024, 5:30 PMYoussef Shoaib [MOD]
04/23/2024, 5:32 PM@RaiseDsl doesn't actually mark Raise, and that's on purpose. either<String, _> { either<Int, _> { raise(42) } } is expected to work.
Hopefully once K2 is out fully, JB will give contexts more love.CLOVIS
04/23/2024, 5:33 PMsimon.vergauwen
04/23/2024, 5:34 PMflatMapLeft is not possible to implementYoussef Shoaib [MOD]
04/23/2024, 5:38 PM@DslMarker would forbid such usages implicitly, one can always use an explicit receiver. I'm also not sure whether context(Raise<A>, Raise<B>) would be forbidden by @DslMarker, but if it's forbidden, it'd be a huge nerf, and if it isn't forbidden, then we get to the same issue that we have here with recover etcCLOVIS
04/23/2024, 5:53 PMYoussef Shoaib [MOD]
04/23/2024, 5:56 PMDslMarker had a way to only get mad based on type parameter value, then perhaps it would be applicable here, but alas.Alejandro Serrano.Mena
04/23/2024, 6:53 PMYoussef Shoaib [MOD]
04/23/2024, 6:55 PMCLOVIS
04/23/2024, 8:06 PMOutcome class, which doesn't have progress informationCLOVIS
04/23/2024, 8:08 PMRaise<Lce<Failure, Nothing>> , which seems a bit strange to me. I won't be able to run raise programs over Raise<Failure> .Youssef Shoaib [MOD]
04/23/2024, 8:11 PMProgressiveOutcome.Unsuccessful
(Aw man I just realised how cool this could be if we had Union types and perhaps a way to remove an element from a Union, then we could do ProgressiveOutcome - ProgressiveOutcome.Successful and perhaps make an easy DSL super-function that does exactly what those docs say to do)CLOVIS
04/23/2024, 8:11 PMNot enough information to infer type variable Failure on both the DSL and recover 😕CLOVIS
04/23/2024, 8:12 PMIncomplete | Failure<F> 🙂Youssef Shoaib [MOD]
04/23/2024, 8:13 PMrecover<ProgressiveOutcome.Unsuccessful<...>, _> should hopefully do the trick, then the outer one will need outcome<Nothing, _> until your example gets more complicatedCLOVIS
04/23/2024, 8:18 PMraise is for the outer scope. I don't like this at all.Youssef Shoaib [MOD]
04/23/2024, 8:20 PMProgressiveOutcomeDsl.recover functionCLOVIS
04/23/2024, 8:20 PMCLOVIS
04/23/2024, 8:20 PMCLOVIS
04/23/2024, 8:22 PMCLOVIS
04/23/2024, 8:23 PMdefine aI'm going to end up the entirety of Arrow within that one class thoughfunctionProgressiveOutcomeDsl.recover
CLOVIS
04/23/2024, 8:28 PMYoussef Shoaib [MOD]
04/23/2024, 8:29 PMrecover, hence why that's the bandaid we went for.
One annoying option is to forego a custom DSL class entirely, and instead use Raise directly, and define all your methods as extensions on Raise<ProgressiveOutcome.Unsuccessful>, even including bind yes which is a shameYoussef Shoaib [MOD]
04/23/2024, 8:30 PMbind callsiteCLOVIS
04/23/2024, 8:30 PMCLOVIS
04/23/2024, 8:31 PMYoussef Shoaib [MOD]
04/23/2024, 8:34 PMnullable, option, etc hence why it was crucial to keep around (and well because this issue only surfaced later). If you can live with a bind(foo) call, then that's the way to go.
Btw, I know your library is multiplatform, but you can still provide nice syntax in the JVM module, so that could be a better trade-off