Youssef Shoaib [MOD]
02/10/2024, 11:34 AM@JvmInline value class PartiallyProcessedResult<T>(val result: Result<T>)
inline infix fun <T, R> Result<T>.myFold(ifSuccessful: (T) -> R): PartiallyProcessedResult<R> = PartiallyProcessedResult(map(ifSuccessful))
inline infix fun <R> PartiallyProcessedResult<R>.ifThrowable(ifThrowable: (Throwable) -> R): R = result.getOrElse(ifThrowable)
// allows
result myFold { ... } ifFailure { ... }
This example is easier to figure out than other more-complex use cases (e.g. if both lambdas need to be available at the same time, then there's no option to inline
, and instead one must pass lambdas around).
This would also allow some nice SQL-like DSLs
EDIT: removed bad initial suggestion of multiple lambdas with no infix in the middleYoussef Shoaib [MOD]
02/10/2024, 11:39 AMJoffrey
02/10/2024, 11:43 AMifFailure
instead of invoke
, and it's more composable tooYoussef Shoaib [MOD]
02/10/2024, 11:45 AMmyFold
function cannot be defined "in one go", as in any data would need to be passed along in the resulting type of myFold
and onto ifFailure
. I also cannot enforce that ifFailure
would be called at all. I had to introduce an intermediate type PartiallyProcessedResult
in this example to nudge the autocomplete to tell the user to call ifFailure
Joffrey
02/10/2024, 11:46 AMifFailure
is not optional, then why not just use named parameters?Youssef Shoaib [MOD]
02/10/2024, 11:48 AMinline super-infix fun <T, R> Result<T>.myFold(ifSuccessful: (T) -> R, ifFailure: (Throwable) -> R): R = fold(ifSuccessful, ifFailure)
So that I can do a call like
result myFold { ... } ifFailure { ... }
Using named parameters makes the syntax a lot worse:
result.myFold(ifSuccessful = { ... }, ifFailure = { ... })
It's a similar question to "why have infix": It's there to make the syntax a bit nicer, especially for DSLsYoussef Shoaib [MOD]
02/10/2024, 11:50 AMinline
support (not only for performance, but to allow contracts and non-local returns)hfhbd
02/10/2024, 11:50 AMJavier
02/10/2024, 11:50 AMUsing named parameters makes the syntax a lot worseIn general I use named params for this
Javier
02/10/2024, 11:51 AMresult myFold { ... } ifFailure { ... }
and infix
function existence.Youssef Shoaib [MOD]
02/10/2024, 11:51 AMYoussef Shoaib [MOD]
02/10/2024, 11:52 AMmyFold
overloads like that. I think it should maybe always prefer one or the other, but again it doesn't matter in most situationshfhbd
02/10/2024, 11:52 AMJavier
02/10/2024, 11:52 AMmyFold { ... }
and this is a problem, why the first lambda does not include the named param and the second one includes it?
Should it be
result myFold ifSuccessful { ... } ifFailure { ... }
Javier
02/10/2024, 11:53 AMYoussef Shoaib [MOD]
02/10/2024, 11:57 AMJavier
02/10/2024, 11:57 AMmyFold(
ifSuccessful = { },
ifFailure = { },
)
myFold
{ }
ifFailure { }
myFold
ifSuccessful { }
ifFailure { }
I have had problems with infix functions when they become multiline with Kotest in the past, for example, it is easy to have a problem when chaining. Sadly I don't remember which was the issue but I remember moving everything to non-infix style in multiline.hfhbd
02/10/2024, 11:58 AMJavier
02/10/2024, 11:59 AM@Composable
fun SomeUiElementWithMultipleClickableItems(
onIconClick: () -> Unit,
onTextClick: () -> Unit,
) { ... }
SomeUiElementWithMultipleClickableItems
{}
onTextClick {}
Youssef Shoaib [MOD]
02/10/2024, 12:09 PMSomeUiElementWithMultipleClickableItems {
...
} onTextClick {
...
}
So that it is similar to a try-catch-finally
or an if-else
To your point @hfhbd, this is perfectly valid Kotlin:
val foo = try { ... } catch (e: Blah) { ... }
I do agree that this is a niche use case. Frankly, if this takes significant dev time, it's not worth it at all!Javier
02/10/2024, 12:19 PMJavier
02/10/2024, 12:19 PMeygraber
02/11/2024, 2:23 AMJavier
02/11/2024, 7:54 AM_unit: Unit = Unit
Wout Werkman
02/12/2024, 2:00 PMfunc myFold(_: () -> (), ifFailure: () -> ()) { }
func foo() {
myFold { ... } ifFailure: { ... }
}
In theory Kotlin could implement this through named arguments. But AFAIK currently there is no way to effect overload resolution by parameter names, and I doubt they are eager to change this.
Additionally, it wouldn't be as flexible as Swift would be:
func foo() {
myFold { ... } ifFailure: { ... }
myFold { ... }
ifFailure: { ... }
// Both fine in Swift
}
But in Kotlin:
func foo() {
myFold { ... } ifFailure = { ... } // Could in theory be implemented
myFold { ... }
ifFailure = { ... } // Would be source breaking change if this would resolve to parameter instead of var/property setter
}
Additionally, I think that using inline functions as an argument isn't going to be as convincing, since it's a micro optimization that will be solved by JIT on hot paths anyways.Youssef Shoaib [MOD]
02/12/2024, 2:02 PMsuspend
and @Composable
through. That's a very powerful argument for why we need themJoffrey
02/12/2024, 2:05 PMmyFold { ... } ifFailure { ... }
instead of myFold(ifSuccess = { ... }, ifFailure = { ... })
. I don't think it passes the -100 points rule to begin with, but also I don't think it is actually desirable from a code style standpoint.Wout Werkman
02/12/2024, 2:17 PMsuspend
and @Composable
through. That's a very powerful argument for why we need them
Yes, but this will still work in both the infix case as well as the named parameters case in the current form. The difference isn't the inline function semantics. AFAIU the use case (aside from much nicer syntax)* is micro optimizing intermediate objects. Which will be eliminated by escape analysis on hot paths.
Don't get me wrong, I just enjoy discussing design and I try to share some arguments I think one would have to defend/improve if you want to turn this into a KEEP 🙂Youssef Shoaib [MOD]
02/12/2024, 2:29 PMCasey Brooks
02/13/2024, 7:17 PM