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