Hopefully this is the right place to ask, if it is...
# getting-started
d
Hopefully this is the right place to ask, if it isn’t, I’ll delete. I have a system that basically runs transformations on a container type, with each operation returning either the argument (the container) or a mutated copy of it (i.e., operations can change the state of the container), with an exception ALSO being a potential result (i.e., the state of the container was such that it was invalid: stop processing.) Coming from java, I wrote this:
Copy code
return runBlocking {
            var localContainer = container;
            operators.forEach {
                localContainer = it.apply(localContainer)
            }
            localContainer
        }
(after all, each operator can do IO to load additional state or whatever, and it’s okay if that takes a while.) However: 1. What’s a more Kotlin way to do this? I keep thinking I should use a
Result
for this. 2.
localContainer
is, at the end of the exception, the original argument, and not the container value at the point of the exception; why?
c
Perhaps something like
operations.fold(container) { it.apply(container) }
d
for slightly more detail, a container has a source object, and can add other objects that relate to it (think “order” and then “events attached to that order” along with any other data), and I was using exceptions to indicate that things were out of what
@Chris Lee does that retain changes to container? Let me check
c
container is the starting value. it.apply would provide a new (or modified) container object.
d
Hmm, the original context is STILL the output of the container, though, even with that
Copy code
return operators.fold(container) { c,operator -> operator.apply(c)}
I know the subsequent operators are getting the right state
c
the fold operation should return what you want to be the “next” value.
d
they do, otherwise some of the subsequent operations wouldn’t work at all
I have four test operations, the last ones require the prior ones to have passed properly
c
not clear where the problem is then? fold will return the final result.
d
hold on, I’ll build a pastebin
bleh, sorry
anyway, there are two containers that run “properly” - one has no billing events, that’s an error, that output is correct (the state is what it should be) but the last two containers should have events associated in the container
and those validations shouldn’t run at all if events isn’t populated when they run
oh wait, maybe I have an incorrect assumption anyway, fixing
nope, I had an incorrect comparator after the change but the result is correct once fixed
I added some extra comments, too.
j
machine.execute will only return after the last succesful operator completes
d
oh, dadgummit
you’re right
j
so the assignment will never happen during an exception
d
it’s throwing away the ref, because of the xception
son of a gun! facepalm moment.
is there a way to do that without a custom exception type? Is there a “more kotlin” way to do this?
c
how do you want to handle exceptional conditions? If it’s via exceptions, you can create a custom exception type and catch that. An alternate approach is to create a sealed class heirarchy of results - Success, Failure at a minimum, more types if you need to discriminate, and return that.
d
I think I’d prefer the latter, because that way I learn new stuff. Got a good pointer for that?
c
Here’s a snipped from some current code:
Copy code
public sealed class ClaimVerificationResult {
    public class Failure(private val claimName: String, private val message: String) : ClaimVerificationResult() {
        override fun toString(): String = "JWT Claim '$claimName' $message"
    }

    public data object Success : ClaimVerificationResult()
}
…that’s a fairly simple one. More complicated cases would have a richer type hierarchy to operate on.
d
Well, doing the exception mechanism thing is trivial - I mean, I’ve been doing Java since 1998 or so. I was trying to avoid try/catch, though, if I could.
and how would you use that?
y
IDK if you want to maintain mutability but personally I’d use copy() when altering the state and Either (from ArrowKt) or Result (from stdlib) so you can handle exceptions gracefully
c
your processing methods would return
ClaimVerificatinResult
and the outer could would do a
when
on the result looking at the type and processing accordingly.
d
@Yonatan Karp-Rudin yeah, that’s part of what I was trying to angle toward
You’ll note that I don’t actually mutate Container at all, it’s copy() or nothing
y
@dreamreal in such case I’ll suggest to use copy() and just replace the specific field you want to use. I think it’s considered my idiomatic 🙃
d
@Yonatan Karp-Rudin that’s exactly what I do for the container mutations
💯 1
🤦‍♂️ 1
c
return c.copy(events = e)
all good there, objects remain immutable.
y
Sorry I misunderstood your message 🤣
d
I’m just trying to wrap my head around Either/Result
y
So result is a simpler version of either where “left” is an exception- and either allows you to return whatever 2 types you want where traditionally right is the “right results” I hope it somewhat helps
d
Yeah, I just don’t know what that looks like in application
y
The beauty is that the result can be either left or right but not both at the same time 😊
Have you checked arrow docs about either? They’re pretty good 😊
d
I’m looking at them right now, but they’re not thorough enough, IMO: the examples they offer are really simple
they’re great for the two-foot end of the pool, and maybe that’s enough, but not for me right now for some reason
c
Something like this as a start into integrating error handling into the control flow:
Copy code
interface Operator2 {
    suspend fun apply(c: Container): OperatorResult
}

sealed class OperatorResult(val container : Container) {
    data class Success(container : Container) : OperatorResult(container)
    data class Failure(val lastContainer : Container, val message : String) : OperatorResult(lastContainer)
}

class Machine2() {
    suspend fun execute(
        container: Container,
        operators: List<Operator2>
    ): Container {
        // start with Success and keep processing until failure, return container
        // alternately this could be operators.map to transform each into a result and then process from there
        return operators.fold(Success(container)) { result, operator -> 
            when(result) {
                is Failure -> result.container
                else -> operator.apply(result.container)
            }        
        }
    }
}
j
Its been a while since I looked at arrow, but whenever I do look at it, I don't notice any features that make me want to adopt it. Plain old kotlin is functional enough for me.