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?.baz
Javier
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 toRaise
what.bind()
is tosuspend
..await()