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