https://kotlinlang.org logo
Title
m

Marko Novakovic

01/27/2023, 9:42 AM
is there any chance that
!
will come back in place of
.bind()
? I really liked it 😄
s

Stylianos Gakis

01/27/2023, 9:43 AM
How did that actually look like? I don’t think I’ve seen it before.
m

Marko Novakovic

01/27/2023, 9:46 AM
am typing it directly here so don’t mind errors 😄
fun a() = Either.Right(1)
fun b() = Either.Right(2)

fun sum() = either {
    !a() + !b()
}
something like that
c

CLOVIS

01/27/2023, 9:49 AM
I really like the idea of having an operator for
.bind
, but I really dislike
!
, it's really not readable to me. One option would be if the Arrow Meta team created their own compiler plugin which added an operator just for that, but… …with Arrow 2.0 we will almost never use
.bind
, so why bother?
context(Raise<Error>)
fun a() = 1

context(Raise<Error>)
fun b() = 2

context(Raise<Error>)
fun sum() {
    a() + b()
}
s

simon.vergauwen

01/27/2023, 9:49 AM
There are no plans to bring is back, but you could already bring it back on the JVM if you don't mind using experimental context receivers for it.
context(EffectScope<E>)
suspend operator fun <E, A> Either<E, A>.not(): A = bind()
With
context
receivers you can indeed get rid of
bind
completely as well 😄 But that'll heavily depend on what style you prefer, I expect many people to still use
Either
.
s

Stylianos Gakis

01/27/2023, 9:50 AM
Damn, I am not gonna lie Marko, I’d be quite upset if I was working on a project and someone brought this kind of syntax in 😂
m

Marko Novakovic

01/27/2023, 9:51 AM
@CLOVIS true
s

simon.vergauwen

01/27/2023, 9:51 AM
…with Arrow 2.0 we will almost never use
.bind
, so why bother?
I'd argue this is more context receivers than 2.0, you can already do the same today in 1.x.x with context receivers.
m

Marko Novakovic

01/27/2023, 9:51 AM
@simon.vergauwen I actually don’t mind using context receivers at all 😄
@Stylianos Gakis hahahaha
c

CLOVIS

01/27/2023, 9:52 AM
But that'll heavily depend on what style you prefer, I expect many people to still use
Either
.
We'll see. Personally this is the syntax that was missing for me to really embrace "Effect is a function, Either is its result after execution" concept
s

simon.vergauwen

01/27/2023, 9:52 AM
Right, you don't have to convince me 😜 but it's hard to predict how the community will embrace context receivers atm.
c

CLOVIS

01/27/2023, 9:53 AM
Context receivers IMO bring Arrow on par with Coroutines with the "yeah all functions should return a Deferred/Either, but let's make it a keyword and you can keep the return type readable" I don't really like having
Either
as a return type everywhere and having to always use
bind
, feels 'un-kotlin-y', like using
await
after each function call
r

raulraja

01/27/2023, 9:55 AM
Also if union types end up in kotlin we can have
context(Deps) Effect<E, A>.run(): E | A
where you can
raise(e)
or
return a
and then there is not even an Either. https://github.com/47deg/TBD/blob/main/scala-fx/src/main/scala/fx/Runtime.scala#L13
s

simon.vergauwen

01/27/2023, 9:57 AM
Yes, that was the motivation behind this improvement @CLOVIS. Kotlin did an amazing job with
suspend
, and the syntax for me is the killer feature and also why Loom is IMO non-impactful for Kotlin since the syntax is the killer feature. Similarly with
?
actually, it is in a very similar spirit as well.
c

CLOVIS

01/27/2023, 9:59 AM
@simon.vergauwen there was a thread about this recently, but Loom is actually very interesting as a coroutine Dispatcher because all JVM blocking calls made inside virtual threads are magically non-blocking! But yeah I'm the same mind that apart from using a different dispatcher, I will definitely write everything using
suspend
m

Marko Novakovic

01/27/2023, 10:01 AM
btw, do you guys have a problem when you enable context receivers but you are still getting compiler errors?
c

CLOVIS

01/27/2023, 10:02 AM
I'm a heavy Kotlin/JS user, I haven't been able to play with context receivers outside of AoC 😕 Sorry but don't have any help with that
s

simon.vergauwen

01/27/2023, 10:04 AM
@Marko Novakovic There are a couple of edge-cases that I ran into issues with context receivers, but they were all fix-able by re-writing them slightly differently. I have a bunch of POC where I refactored projects to use context receivers everywhere. @CLOVIS Yes exactly. What I meant is that it doesn't make Coroutines obsolete in any way.
m

Marko Novakovic

01/27/2023, 10:06 AM
but they were all fix-able by re-writing them slightly differently
I have pretty basic example, one from above,
context(Raise<Error>)
fun a() = 1
but getting error
s

simon.vergauwen

01/27/2023, 10:06 AM
What error are you getting?
m

Marko Novakovic

01/27/2023, 10:07 AM
The feature "context receivers" is experimental and should be enabled explicitly
but I have
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().all {
    kotlinOptions {
        jvmTarget = "11"
        freeCompilerArgs += listOf("-Xcontext-receivers", "-opt-in=kotlin.RequiresOptIn")
    }
}
inside
build.gradle.kts
, shared KMM module
s

simon.vergauwen

01/27/2023, 10:08 AM
Ah, they only work for JVM atm 😞
c

CLOVIS

01/27/2023, 10:08 AM
@Marko Novakovic context receivers are only a thing for JVM currently 😕
m

Marko Novakovic

01/27/2023, 10:08 AM
………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
am such an idiot 😄. thank you
c

CLOVIS

01/27/2023, 10:08 AM
Well, it's quite a big feature so it's normal that they do it step by step, but yeah I really want to play with them 😅
m

Marko Novakovic

01/27/2023, 10:09 AM
makes sense but it didn’t cross my mind at all
c

CLOVIS

01/27/2023, 10:09 AM
I don't think they said why it's JVM-only for the moment, but I assume it has something to do with the progress of K2
p

phldavies

01/27/2023, 10:20 AM
I’m really excited by context receivers but they are still a little bit buggy. My favourite at the moment has to be the fact once you have multiple context receivers you can resolve any extension on any type. https://youtrack.jetbrains.com/issue/KT-53391/Incorrect-extension-resolution-when-multiple-context-receivers
I’ve hit this ambiguity a couple of times when it comes to multiple effect/raise scopes, but it’s always possible to rewrite to workaround it - but you shouldn’t have to.
o

Olaf Gottschalk

02/20/2023, 8:11 PM
Looking at the examples using context receivers with the Raise "effect" - where is the difference of this style to checked exceptions in Java? Essentially we are marking our funs with what they could throw / raise... can somebody try to help me understand where the big difference is?
s

simon.vergauwen

02/20/2023, 8:14 PM
The idea of Java and checked exceptions is good, but the implementation is pretty bad in comparison. The two most significants ones are probably: • checked exceptions and lambdas don’t work together • Checked exceptions and concurrency/parallelism don’t work together
Raise<E>
and
Either
are equivalent and isomorphic, but different notation. In theory you can also compare
Either
with checked exceptions but
Raise
looks more familiar in syntax.
Other modern FP languages are also moving towards this model of “capabilities” rather than wrappers like
IO
,
Either
, etc since “capabilities” can be composed but monads cannot.
p

phldavies

02/20/2023, 8:25 PM
The other difference I can see (although I may be wrong here) is context receiver of
Raise<E>
effectively provides a handler, which can be as lightweight or heavy as required, for errors directly to the function being called (and thus must be available) whereas checked exception mechanism is forced throughout the entire stack for propagation.
o

Olaf Gottschalk

02/20/2023, 8:25 PM
Ah, thanks @simon.vergauwen! So, I was seeing it correctly. Stating throws XyzException is also like a capability. But hasn't one problem of checked exceptions also been that the exception types of certain libraries "leaked" all around the place in projects? But that was maybe more due to lazy programmers just bubbling them up instead of transforming them on the way up...
@phldavies yes that is also true, sure. Exceptions are not light weight... I was looking at it more from the functional side, less from the implementation side.
p

phldavies

02/20/2023, 8:30 PM
There's also the fact that checked exceptions, by necessity of the underlying mechanism, have a forced type hierarchy root whereas there are no such restrictions on
Raise<E>
and isn't forcibly conflated with truly exceptional flows.
s

simon.vergauwen

02/20/2023, 8:36 PM
But that was maybe more due to lazy programmers just bubbling them up instead of transforming them on the way up...
That is kind-of a design decision I guess 😁 They definitely got abused a lot in Java, but not really sure what the root of that was. I think it was a combination of a lot of bad practices. Perhaps it’s also a better thing that in Kotlin these concepts live in libraries and not in the language / Std. Similarly to how KotlinX Coroutines is a library, and not like Future in Scala.
s

Stylianos Gakis

02/20/2023, 11:02 PM
Speaking of exceptions and having them leak out from libraries. What do you think about libraries exposing public API which includes such signatures? A context receiver of
Raise<E>
for example? As a consumer of some library, would you prefer this which would then also need to expose arrow's api, or thrown exceptions? Or their own result type which wouldn't force any dependency. Or if none of the above, what else?
m

Marko Novakovic

02/21/2023, 11:03 AM
I am in a process of writing a blog post on this exact topic and this provides some additional information 😄, what a coincidence. thank you 😄
s

simon.vergauwen

02/21/2023, 11:10 AM
@Stylianos Gakis it's hard to give a one-fits-all answer here.. It heavily depends on what kind-of library you're building. Often in Scala, exceptions will also still be used or allowed to passthrough. Staying close to original implementation of JDBC, when building a database layer for example is often desirable since a lot of documentation and information can be found about JDBC. Building a completely custom library, not relying on a lot of code underneath I would personally not to throw exceptions. If already relying on Arrow, why not expose it's
Either
type or
Raise
if context receivers are available. I.e. https://github.com/nefilim/kjwt. A custom ADT could also work, if you're not relying on Arrow for the internals of your library and in that case an arrow-integration module should be quite easy to offer. With context receivers you could even offer Arrow's
bind
syntax for your custom ADTs.
s

Stylianos Gakis

02/21/2023, 11:20 AM
Right, but then you can’t just expose arrow’s
Either
, you’d have to have that dep as
api
and expose all of it. Which comes with the problem that if your lib has a different arrow dependency, then you are exposing that and so on. But yeah, I see what you mean, and I think the idea of keeping it as original as possible for use cases like JDBC makes a ton of sense, while it may not if you are building a very specific library which advertises that it exposes some nice APIs using arrow like this kjwt does. Thanks a lot for the response!
p

phldavies

02/21/2023, 11:22 AM
It may not be intended, but it seems the compiler will preference resolving a context-receiver method if the context is available over the non-context-receiver method, allowing a default context to be provided in an overload. This can provide a "bridge" for exception-consuming worlds that don't want to utilise
Raise<E>
without leaking
Either
or
Raise
s

simon.vergauwen

02/21/2023, 12:31 PM
Which comes with the problem that if your lib has a different arrow dependency, then you are exposing that and so on.
Which is why Arrow makes a big point of being binary compatible, and following semantic versioning. This should never be an issue within major versions. In the end the Kotlin Std, KotlinX Coroutines, RxJava/Project Reactor, Guava, Apache Commons, etc also suffers from this issue, and in the Kotlin Std I've even come across binary incompatibility issues minor version albeit relying on
@ExperimentalTime
so I willingly holding a gun in my hand... Additionally, a big priority of Arrow 2.x was to make the binary really small. Which is a second benefit of making the API more uniform, and getting rid of dead weight. By doing this the threshold of exposing Arrow should be lower, at least from a binary size point of view in contrast to for example RxJava/Reactor/Guava which have much bigger binaries than Arrow even 1.x.x. This was a big issue for Arrow pre 1.x.x already.
It may not be intended, but it seems the compiler will preference resolving a context-receiver method if the context is available over the non-context-receiver method, allowing a default context to be provided in an overload.
This is intended by the Kotlin language, and it's actually a great technique for overloading top-level functions within a receiver lambda. The reasoning is that if you're inside of
R.() -> A
then
this
is
R
and you're thus calling method functions instead of top-level and methods have precedence of top-level. So it's behavior that was explicitly designed like this in Kotlin.
p

phldavies

02/21/2023, 12:34 PM
I've been assuming it was intended, but hadn't seen it explicitly stated as such. :thank-you: