Rob Elliot
02/21/2024, 10:29 AMbar*.foo() dereference operator to mean bar.map { it.foo() } and possibly a bar**.foo() to mean bar.flatMap { it.foo() }, in the same way that we have bar?.foo() to mean if (bar == null) null else bar.foo().Javier
02/21/2024, 10:46 AMRob Elliot
02/21/2024, 10:47 AM?. - because it allows chaining & reduced nestingJavier
02/21/2024, 10:49 AMJavier
02/21/2024, 10:52 AMval baz: Baz? = foo?.bar?.baz
bar
.map { it.foo }
.filter { ... }
// or
bar
.map(Bar::foo)
.filter { ... }
// vs
bar*
.foo()
.filter { ... }Rob Elliot
02/21/2024, 10:53 AMQuestion mark is more intuitive in my opinionI suspect that's a case of the "syntax I am familiar with is more intuitive that syntax I have not personally encountered before" cognitive bias
Javier
02/21/2024, 10:53 AM* operator already exists in KotlinRob Elliot
02/21/2024, 10:54 AM*, though Groovy uses * in essentially this wayJavier
02/21/2024, 10:54 AMJavier
02/21/2024, 10:54 AMval baz: Baz? = foo?.bar?.bazJavier
02/21/2024, 10:59 AMI suspect that's a case of the "syntax I am familiar with is more intuitive that syntax I have not personally encountered before" cognitive biasThe nullability system in Kotlin is based on
?, to indicate types, etc. It has a massive usage in a Kotlin basic feature. So it is intuitive to think ? is related to nullability when it is seen as an operator. I don't similarly see the other.Rob Elliot
02/21/2024, 11:00 AMfoo, bar and baz all throw exceptions I can write foo().bar().baz().
In an Either world I have to write foo().flatMap { it.bar() }.flatMap { it.baz() }, which I think introduces substantial cognitive overhead dealing with the nesting. I think foo()**.bar()**.baz(). leaves it clearer to the intent, to chain the happy path route.
Anyway, not likely to happen, so not worth getting worked up about. Just a pain point to be accepted.Javier
02/21/2024, 11:03 AMwasyl
02/21/2024, 11:04 AMbar*.foo() == bar.map { it.foo() }, what type is bar? Is it a collection or Either?Rob Elliot
02/21/2024, 11:05 AMRob Elliot
02/21/2024, 11:07 AMWout Werkman
02/22/2024, 10:54 AMRaise context, such as the either function.Javier
02/22/2024, 11:02 AMRaise API was removing the need to bind indeed. I haven't more detail about this specific use case, cc @simon.vergauwenCLOVIS
02/22/2024, 11:04 AMRaise is essentially to .bind() what suspend is to .await() .
Just like coroutines remove all need to .await() after calling each function, Arrow's Raise removes the need to .bind() after calling each function, so you can just chain them normally.CLOVIS
02/22/2024, 11:05 AMJavier
02/22/2024, 11:07 AMfoo.bar.baz?CLOVIS
02/22/2024, 11:08 AMfoo , bar and baz are getters with a Raise context receiver, yes.CLOVIS
02/22/2024, 11:10 AMEither<Ohno, Bar> as argument.
This function requires Raise<Ohno> . So it can only be called when the caller knows how to handle Ohno . For example:
either<Ohno, Baz> {
val foo: Either<Ohno, Bar> = β¦
a(foo)
}
well, the user could just write
either<Ohno, Baz> {
val foo: Either<Ohno, Bar> = β¦
a(foo.bind())
}
and now a only receives valid valuesCLOVIS
02/22/2024, 11:21 AMRaise is the "world of computation": it's the function's type
β’ Either is the "world of values": it's a representation of the result of a function that can fail
Traditionally, we would write many functions that work with Either :
suspend fun getUser(id: String): Either<NotFound, User> = β¦
suspend fun getName(user: User): Either<NotFound, String> = β¦
which can be combined with flatMap :
getUser("123").flatMap { getName(it) }
Raise works directly at the function level. There are no more special return types anymore:
context(Raise<NotFound>) suspend fun getUser(id: String): User = β¦
context(Raise<NotFound>) suspend fun getName(user: User): String = β¦
because you cannot call a contextual Raise function when you don't have the error management in place, no explicit syntax is necessary:
getName(getUser("123"))
.bind is just the conversion function from Either to Raise . If you use Raise everywhere, you never need to convert, and thus you never need any special syntax.simon.vergauwen
02/22/2024, 1:24 PMRaise and context parameters might be what you're looking for. At least most maintainers have always seen them as the "redemption" of wrappers like Either, and operators like `map`/`flatMap`. If you get rid of those, there is actually very little functionality that you actually need. raise, and recover is all you need. All the rest is just regular function application.
This is going to be one of the topics of my talk during KotlinConf βΊοΈsimon.vergauwen
02/22/2024, 1:25 PMβ€οΈis essentially toRaisewhat.bind()is tosuspend..await()