Hi, not sure if this is the best place for this di...
# arrow
t
Hi, not sure if this is the best place for this discussion, but it's tangentially related to Arrow, so I hope it's ok. For a while now I'm thinking about a unified way to design functions that can fail. I came up with basically copying
Either::getOrElse
. The behaviour I like there is being able to affect the return type in the recover block.
Copy code
val a: Either<Something, Int>
val r1: Int = a.getOrElse{ 0 }
val r2: Int = a.getOrElse { throw ... }
val r3: Int = a.getOrElse { raise(...) }
val r4: Int? = a.getOrElse { null }
While playing around I came up with:
Copy code
inline fun <ERR, A, B : BB, BB> A.toB(
    transform: Raise<ERR>.(A) -> B,
    recover: (ERR) -> BB,
) = fold({ transform(this@toB) }, { recover(it) }, ::identity)
So now I can do (just as example):
Copy code
val r1: Long? = "abc".toB({
    it.toLongOrNull() ?: raise(Unit)
}) {
    null
}
The behaviour is correct but it'd be nice to wrap it in a function
String::toLongOrElse
The issue is how would you implement that?
Copy code
inline fun <T: Long?> String.toLongOrElse(recover: () -> T): T =
    this.toB({ 
        it.toLongOrNull() ?: raise(Unit)
    }) {
        recover()
    }
This doesn't really work since the transform requires to return
T
but returns`Long`
s
I think you're missing a generic constraint in
toB
for this to work 🤔
Actually, why
T : Long?
🤔 It can only be
Long?
so no generic is needed.
Ah, you want it to be nullable if
recover
returns a nullable but non-null if there is a non-null
Long
returned from
recover
? 🤔
t
toB
when used on it's own works well. It infers common parent from return types of transform and recover. The issue is wrapping it in a function for a concrete type. I'm able to implement
Either::getOrElse
with it without issues:
Copy code
fun <ERR, B : BB, BB> Either<ERR, B>.getOrElse(recover: (ERR) -> BB) = toB({
    it.fold(
        ifLeft = { raise(it) },
        ifRight = ::identity
    )
}) {
    recover(it)
}
Yes. I want it to infer common parent and use it as a return type.
TBH I don't really see what's blocking the inference. Since I can make it work when directly calling
toB
and even when implementing the
getOrElse
the inference is possible, (at least in some cases).
s
Hehe, this is a tricky thing. The compiler knows that
T : Long?
but not that
Long
is intersects with
T
in a generic environment, but when doing it in a "concrete" environment it can infer the upper bound correctly.
t
My understanding was that the
Long?
extends
Long
. Am I not correct? You can assign
Long
to
Long?
but not vice versa so in that case compiler can infer the relationship.
s
Yes,
Long
extends
Long?
but
Long
is not perse
T : Long?
since
T
can be a different subtype
So either of this is valid:
Copy code
inline fun <T: Long?> String.toLongOrElse(recover: () -> T): Long? =
    this.toB({ 
        it.toLongOrNull() ?: raise(Unit)
    }) {
        recover()
    }
t
Thanks, makes sense. So basically the compiler cannot infer that there is only one possible subtype (understandably). But in the concrete environment it operates on the generics without bounds, so it infers it correctly. I guess I have to wait for union types (fingers crossed) to get this to work (if there's gonna be common parent inference).
s
My pleasure ☺️ Yes, that is exactly what is going on. I've ran into this a couple of times, some other languages can do this correctly but it's quite tricky.