Hey new to Arrow here, using only Core so far and ...
# arrow
s
Hey new to Arrow here, using only Core so far and I got a question, probably a bit stylistic about how code looks like. I often find myself at the end of a chain where I got an
Either
to want to do something with the result, whether it’s left or right. 1. I see there’s one alternative of assigning the result to a value and doing a
when
on it, which works but feels a bit “extra”. 2. I can do a
tap
and a
tapLeft
which i don’t like because I’m doing it in two functions. 3. There is also
fold
which is what I am using the most, but this also comes with folding the two types into one which isn’t what I am looking to do in those scenarios. I would like to be able to have an alternative that does basically what tap/tapLeft does but for both cases. And now I wonder, does this not exist right now because: 1. It’s too easy to simply do our own implementation of it 2. No good name exists to denote such a behavior 3. It’s generally not a good idea and if I am looking to do something like this I probably want to do something else instead And I feel like the 3rd option is what’s happening here so I wanted to bring this up here to see what people generally do in those cases 🤔
s
Typically people are using
fold
, you can also use
fold
in combination with
also
. This is not a very common use case, at least not in my experience. Most code with
Either
is written in
either { }
blocks, and then you can typically achieve such behavior like so.
Copy code
either {
  ....
  x().bind().also { }
}.tapLeft { }
We could consider adding
bitap
which combines
tap
and
tapLeft
but we've become a bit hesitant of adding new APIs since in the last two years we've had a hard time deprecating and removing some badly designed and unpopular APIs to make Arrow more Kotlin idiomatic. That of course doesn't take away that if a feature is popularly requested, we should support it.
What is typically your use case for wanting to inspect both sides of the
Either
? This is something I see typically done for logging, and in most places if seen something like this.
Copy code
suspend fun Either<E, A>.log(logger: Logger): Either<E, A> = also { when(this) { ... } }
Or
also { fold(...) }
r
In addition to what Simon added, if you want to add these in your code base you could have something like
Copy code
fun <E, A> Either<E, A>.tapBoth(f: (E?, A?) -> Unit): Either<E, A> =
  also { fold({ f(it, null) }, { f(null, it) }) }

fun <E, A> Either<E, A>.tapBoth(ifLeft: (E) -> Unit, ifRight: (A) -> Unit): Either<E, A> =
  also { fold(ifLeft, ifRight) }
s
Yeah this tapBoth seems very straightforward, depending on how this conversation goes we might introduce it! It’s actually the first time I see this
either
builder, very interesting, but also quite foreign syntax with the
bind()
. I’m sure it’ll get more familiar once we find use cases and play around with it a bit more. With that said, I’ll link you to a draft PR we’re looking into which sparked this conversation. This is an example of a place where we’re doing that, since we’re experimenting with our UseCases returning
Either
types and then handling each case in our call site. Along with places like this which made me think that since we don’t have this
tapBoth
standard, we’re bound to sometimes while writing the code get carried away and do the same thing in different ways, like this one.
These are btw our “baby steps” into arrow core, basically we opted into using it because we wanted a better Result type than the one built-in to Kotlin so if we’re doing anything else in an odd way feel free to point it out!
s
If you're not yet familiar with the
either { }
block I suggest looking into that. It's the most idiomatic way of using Arrow, and doing FP in Kotlin.
bind()
allows you to write imperative code rather than callback-based code using
flatMap
.
Copy code
either {
 val x: Int = x().bind()
 val y: Int = y().bind()
 x + y
}
Copy code
x().flatMap { x ->
  y().map { y ->
    x + y
  }
}
The
either
block also exposes interesting syntax such as
ensure
and
ensureNotNull
which work like
require
and
requireNotNull
but with
E
instead of
Throwable
. I see in your PR that you're mostly only using
map
and in a couple of places
flatMap
which I think can be replaced with
ensureNotNull
and
ensure
. Here is a blog post talking about the new runtime for computation blocks that is going to be introduced in Arrow 1.1.x (next weeks). https://nomisrev.github.io/continuation-monad-in-kotlin/
https://github.com/HedvigInsurance/android/pull/918/files#diff-eeb8d2e0216a5ebdcd1009b91949a27d5b21b0b8c87312194887d058921a7393R398-R399 Here I would still opt for
fold
instead of
tapBoth
though, since you're consuming both right and left values by either assigning it to a backing field or
trySend
.
TBH I've switched almost completely to
when
over
fold
though since I feel it reads more idiomatic but that's purely a style preference since in the bytecode it's 100% equivalent.
s
Yeah this was the first thing I’ve written with
either
seems pretty cool indeed. I’ll definitely look more into it! Thanks for sharing that post too,
ensure
also looks very interesting! So smart to have an alternative to require that maps to our own type instead of
Throwable
. Looking forward to play with this too 😄 But to answer the original question, for the existing code we have I guess then we should just introduce the tapBoth() function as I personally like the fact that it’s a fold that doesn’t change the type of the original Either. And yeah in that case where we do trySend as you said fold is amazing since we do actively want to convert both left and right to a common type which is sent as an event, so there it makes total sense. I just felt like it’d be nice to have an explicit function for the cases where that’s not the case, to make that intent explicit. Interesting that you’ve switched over to going with
when
though, I’ve been back and forth on this but I ended up thinking that I like not having to either a) put the entire chain inside the brackets() of the when b) assign the result to a val just to do a when on it. Which is why I am leaning towards the
tapBoth
. Maybe I change my mind too in the future, who knows 🤔
👍 1
d
maybe Structured Concurrency can use the same style:
Copy code
async(context) {
 val x: Int = x().await()
 val y: Int = y().await()
 x + y
}
s
The
either
DSL is actually build on top of coroutines similar to how Structured Concurrency (KotlinX) is build on coroutines from Kotlin Std. The
async
code you shared has a subtle bug though 😛 You should use
listOf(x, y).awaitAll()
to await both
Deferred
in parallel, Arrow Fx offers
parZip({ x() } , { y() }) { x, y -> x + y }
to avoid that bug 😉
s
I’m so glad I posted this question here btw, we’re having some nice discussions internally after all this. I am sure this will only make our lives easier, we’re all eager to play more with all these ideas! Thanks so much for the help, and even for going out of your way to comment on our PR! I really appreciate it!
🙌 1
s
Always happy to help! If you have any more questions, or thoughts or feedback be sure to share them here 🙂
🙌 1