simon.vergauwen
12/01/2022, 9:17 AMeffect
and Effect
directly?
How problematic would a small migration be for your codebase? The migration would entail refactoring arrow.core.continuations.*
to arrow.core.raise.*
and adding some imports for methods that become extension methods.
For those using Either
& either
or similar it would only require refactoring arrow.core.continuations.either
and arrow.core.continuations.ensureNotNull
to arrow.core.raise.*
without any additional changes.
Within the Arrow team we discuss back-porting the 2.x.x Raise
API to 1.x.x such that migration is eased. I'm trying to figure out how much the effort would be for existing users.CLOVIS
12/01/2022, 9:18 AMsimon.vergauwen
12/01/2022, 9:21 AMTies
12/01/2022, 9:33 AMTies
12/01/2022, 9:33 AMsimon.vergauwen
12/01/2022, 9:34 AMCLOVIS
12/01/2022, 9:35 AMEffectScope
with @Deprecated(β¦, ReplaceWith("Raise", "the import"))
could be enough?CLOVIS
12/01/2022, 9:35 AMsimon.vergauwen
12/01/2022, 9:46 AMimport arrow.core.continuations.effect
import arrow.core.continuations.ensureNotNull
suspend fun main() {
effect<String, Int> {
var x: Int? = null
ensureNotNull(x) { "x was null" }
}.fold({ println(it) }, { println(it })
}
@Deprecated(
"Use the arrow.core.raise.effect DSL instead, which is more general and can be used to and can be used to raise typed errors or _logical failures_\n" +
"The Raise<R> type is source compatible, a simple find & replace of arrow.core.continuations.* to arrow.core.raise.* will do the trick.",
ReplaceWith("effect(f)", "arrow.core.raise.effect")
)
If you apply this ReplaceWith
you get
import arrow.core.raise.effect
import arrow.core.continuations.ensureNotNull
suspend fun main() {
effect<String, Int> {
var x: Int? = null
ensureNotNull(x) { "x was null" }
}.fold({ println(it) }, { println(it })
}
// import missing ensureNotNull
// import missing fold
Of course, clicking alt+enter
to automatically add imports solve the problem but it's not seamlesslysimon.vergauwen
12/01/2022, 9:47 AMCLOVIS
12/01/2022, 9:48 AMReplaceWith
. I personally don't care about unused imports, a properly configured IDE will remove them.CLOVIS
12/01/2022, 9:48 AMsimon.vergauwen
12/01/2022, 9:50 AMCLOVIS
12/01/2022, 9:51 AMsimon.vergauwen
12/01/2022, 9:53 AMReplaceWith
seems too limiting for evolving libraries easily without burdening users.simon.vergauwen
12/01/2022, 9:54 AMeither { f(bind()) }
as cheap as map
, and thus this DSL is cheaper than using flatMap
.simon.vergauwen
12/01/2022, 9:55 AMx
for x
amount of bind
calls.CLOVIS
12/01/2022, 9:55 AMCLOVIS
12/01/2022, 9:55 AMCLOVIS
12/01/2022, 9:57 AMTies
12/01/2022, 9:58 AMsimon.vergauwen
12/01/2022, 10:01 AMinline
, which require extensions and thus imports.CLOVIS
12/01/2022, 10:01 AMcontext(Raise<T>)
is basically Java's throws
keyword, but properly implemented (not slow like exceptions, you can use a proper DSL to handle errors instead of having to write a tryβ¦catch
block).simon.vergauwen
12/01/2022, 10:02 AMCLOVIS
12/01/2022, 10:03 AMCLOVIS
12/01/2022, 10:04 AMrequire
: the developer who called the function passed some data they shouldn't have
β’ ensure
: the user who indirectly called this function should be notified that something wrong happened
β’ check
: the developer who wrote this function did something wrongCLOVIS
12/01/2022, 10:07 AMbind
as an operator. I understand why the Kotlin team doesn't want us to declare arbitrary operators, though.simon.vergauwen
12/01/2022, 10:10 AMbind
can disappear as well, but ideally you have context receivers for this. I.e. when you have context(Raise<E>)
and ensure
, ensureNotNull
or raise
then bind
is never needed.
Similarly for Resource
from Arrow Fx, here is a PR where I remove bind
. https://github.com/47deg/gh-alerts-subscriptions-kotlin/pull/46simon.vergauwen
12/01/2022, 10:11 AMCLOVIS
12/01/2022, 10:13 AMsimon.vergauwen
12/01/2022, 10:15 AMCLOVIS
12/01/2022, 10:15 AMCLOVIS
12/01/2022, 10:16 AMTies
12/01/2022, 10:16 AMTies
12/01/2022, 10:17 AMCLOVIS
12/01/2022, 10:18 AMCLOVIS
12/01/2022, 10:20 AMsimon.vergauwen
12/01/2022, 10:23 AMEither
and bind
.
We as a community need to teach companies that Kotlin is a new language, not Java in disguise.This is IMO also the biggest problem! People preferring Project Reactor with Spring rather than adopt Coroutines is π€― Similarly, context receivers are extremely simple but people (like myself) have been throwing examples around without really explaining what they do or how they work. It's almost the same as adding a parameter to a function. Better IDEA support will be important, i.e. getting a message
Raise<String> is not in scope...
is not useful. If Arrow could customise the message to To handle Raise<String> either add it to the context, or wrap in raise { }
would be great.
The biggest thing holding innovation back is people unwilling to learn and let go of existing habits. That is true for all languages, and new patterns.simon.vergauwen
12/01/2022, 10:23 AMReplaceWith
seems to break the refactor, rather than ignore the unused imports πTies
12/01/2022, 10:25 AMCLOVIS
12/01/2022, 10:26 AMcontext(UI)
@Composable
fun foo() { /* use common APIs here */ }
context(NoUI)
@Composable
fun foo() { /* you can use remember etc, but not emit elements */ }
context(CLI)
@Composable
fun foo() { /* you can use only components that have a CLI implementation */ }
simon.vergauwen
12/01/2022, 10:27 AM(coming from a person who came to Kotlin through Java)Didn't we all π Here is a small blog explaining how they work, https://nomisrev.github.io/context-receivers/
Ties
12/01/2022, 10:27 AMCLOVIS
12/01/2022, 10:27 AMTies
12/01/2022, 10:28 AMTies
12/01/2022, 10:29 AMCLOVIS
12/01/2022, 10:30 AMbind
, without understanding how it's implemented (who does anyway π
continuations are pure black magic at this point)Ties
12/01/2022, 10:30 AMCLOVIS
12/01/2022, 10:31 AMEither<T, A>
by a receiver of EffectScope<T>
. The current documentation explains EffectScope
very well.simon.vergauwen
12/01/2022, 10:32 AMContext receivers won't be very visible in regular user code.That can depend on who you ask π If I ask some of my colleagues, they would use it for DI, Error Management, Effect Tracking (
ResourceScope
, DbScope
, etc) ..
Continuation
is just IO
from FP π
One you got how it works, they're quite simple but I don't think you need to understand how they work in order to use them. IMO KotlinX Coroutines is a quite low-level library similarly to java.util.concurrent
. That's why we have Arrow Fx Coroutines to promote higher-level APIs that don't require anything to understand them.
I.e. listOf(1, 2, 3).parTraverse { }
(renamed to parMap { }
in 2.x.x) is quite simple to understand.simon.vergauwen
12/01/2022, 10:32 AMCLOVIS
12/01/2022, 10:32 AMTies
12/01/2022, 10:33 AMP A
12/01/2022, 4:40 PMsimon.vergauwen
12/02/2022, 7:47 AMCody Mikol
12/03/2022, 5:44 PMsimon.vergauwen
12/03/2022, 6:27 PMEffect
will never replace Either
.
Effect
& EagerEffect
are now an interface
but will become typealias
for suspend Raise<E>.() -> A
, and EagerEffect
will be a typealias
for Raise<E>.() -> A
.
Now you have to distinct between either { }
and either.eager { }
but with this new improvement that won't be necessary anymore.
The cool thing about Raise
is that it allows you to ignore wrappers all together since it represents a computation.
fun Raise<String>.program(): Long = raise("failure")
Where Either
is the result of the computation.
val result: Either<String, Long> = either { program() }
When using Raise
with context receivers you can easily compose different errors, which is impossible with Either
.
context(Raise<String>, Raise<Int>)
fun program2(): Long = raise(1)
And you can then recover from them ad-hoc:
context(Raise<String>)
fun program3(): Long =
recover({ program2() }) { i -> i.toLong() }
I get that effect {} can bind Either / Option / ValidatedAll the DSLs are implemented through
Raise
(and EffectScope
). Looking at the either.eager
or new either
inline fun <E, A> either(block: Raise<E>.() -> A): Either<E, A>
.
So you can always seamlessly mix the two, and you can use what you prefer.simon.vergauwen
12/03/2022, 6:27 PM