What you call outfix functions is more generally known as mixfix or free form syntax.
In fact, it has already been proposed in this post, in a different context, and in the post are discussed some workarounds to achieve it.
This is a popular feature among proof assistants and languages oriented towards formal reasoning, since mathematicians (myself included) consider it an important language feature.
For example, consider the macros or free form syntax supported by Lean 4, which is flexible enough that you could theoretically define Kotlinâs syntax and import Kotlinâs syntax in some files of your project to write some parts in Kotlin (if youâre willing to also implement Kotlinâs semantics), for an outlandish example.
One rationale for this is that often in math, finding the right notation is half the battle; as good notation lifts a substantial part of the complexity of a problem to the syntax level, where even without proper intuition, deduction can be easily performed by anyone who understands its basic rules, even a computer.
In pragmatic languages, however, such as Kotlin, allowing this kind of complex reasoning with expressions is not a priority of language design.
In fact, you could argue the opposite: designing the language so that minimal reasoning and context are required to understand the semantics of any expression makes the language easier to read and learn, and makes it hard to hide errors in artificial complexity introduced by the syntax.
For this reason, following the design philosophy of Kotlin, I donât think weâll see this sort of feature any time soon, at least not with so much flexibility.
It is easy to provide simple examples where allowing mixfix syntax in the language makes things more readable, but itâs just as easy to abuse it.
Besides, Kotlin is already pretty expressive for how explicit it is.
For example, a convenient syntax for the performTaskWhenConditionsAreMet
function that motivated your idea can be simply achieved by swapping the argumentsâ order to benefit from the trailing lambda syntax that already exists in Kotlin:
// Usage
fun main() {
whenCondition(::buttonClicked) {
mainViewNavigation()
}
}
// Implementation
fun whenCondition(condition: () -> Boolean, task: () -> Unit) {
// Do whatever registration needs to happen here
performTaskWhenConditionsAreMet(task, condition)
}
Or, more generally, you could define a simple DSL (known as type-safe Builders in the documentation), if you expect your wiring to become complex:
// Usage
fun main() {
whenCondition(::buttonClicked) {
// Within this block `this` is a `TaskDeclarationScope`, defined below
perform { mainViewNavigation() }
acquireModalFocusUntil { buttonReleased() } // silly example
}
}
// DSL interface
interface TaskDeclarationScope {
fun perform(task: () -> Unit)
// It's hard to come up with good examples when `perform(task)` is already
// so general, but I hope you get the idea
fun acquireModalFocusUntil(condition: () -> Boolean)
}
// DSL implementation
class TaskDeclarationScopeImpl : TaskDeclarationScope {
val declaredTasks = mutableListOf<() -> Unit>()
var acquiresModalFocusUntil: (() -> Boolean)? = null
override fun perform(task: () -> Unit) = declaredTasks.add(task)
override fun acquireModalFocusUntil(condition: () -> Boolean) {
acquiresModalFocusUntil = condition
}
}
// Function implementation
fun whenCondition(
condition: () -> Boolean, tasks: TaskDeclarationScope.() -> Unit
) {
val declarations = TaskDeclarationScopeImpl().apply(tasks)
declarations.declaredTasks.forEach { task ->
performTaskWhenConditionsAreMet(condition, task)
}
declarations.acquiresModalFocusUntil?.let { condition ->
// ...
}
}
(That being said, your example usage reeks of event-handler-hell UI framework design, so, just in case you are not aware, you should consider using or learning from Compose, currently the most idiomatic UI framework for Kotlin.)
The examples you provided in the initial question are easily written as plain infix functions, extension functions, or simply using ==
in these concrete cases. I understand they were fabricated to illustrate the concept and donât represent a real scenario.