Language Design: outfix function

This is a wild idea but it is worth sharing here.

With small tweaks in the semantics of the Kotlin language it is possible to add even more variety to the code style and more freedom in expressions.

An outfix function could be declared as below:

// definition

inline outfix fun <reified T> Any.``is${it}Object``(it: KClass<T>) = this == it

// usage

fun main(vararg args: Any) {

    if (args.firstOrNull() ``is String::class Object``) {

        // ...

    }

}

// or, further more

inline outfix fun <reified T> Any.``${this}is${it}Object``(it: KClass<T>) = this == it

if (``args.firstOrNull() is String::class Object``) { /* ... */ }

I did a quick bit of web searching, but was unable to find a conclusive answer, so I’ll ask: what is an outfix function? I understand an infix function in the context of Kotlin; it’s a function with two parameters, and the first parameter goes before the function, and the second parameter goes after the function.

There is no official outfix function. It is just an idea to extend the language even further.

An infix function is what you described. It is a function that allows the dot-notation to be skipped in its syntax.

Kotlin is designed to be more humanly readable than Java. It would be nice to see more variety in its syntax.

More variety in syntax does not necessarily make for greater readability


(It makes the language that bit harder to learn, to analyse, to write tools for, and increases the opportunities for bugs and for writing bad code. So the benefits would need to be pretty significant to outweigh all that. — For a specific counterexample, consider the cautionary tale of Perl, whose reputation for unreadability may have some connection with its official slogan “There’s more than one way to do it”.)

Infix functions are used very sparingly in Kotlin, mostly so that operators (such as bitwise operators) can be implemented in the standard library instead of in the language (as they are in most languages). They work very well for that; but much wider use would arguably harm, rather than help, readability.

So can you explain more about your use case for ‘outfix’ functions? I don’t really follow the two examples so far — especially since I find them much harder to read than the obvious alternatives.

I see. Sure, I’ll add more input.

Infix functions in Kotlin are purely cosmetic customizations of functions. Anywhere there is a single parameter defined in a function, the funciton may be converted into an infix form allowing it to be expressed differently. This does not mean that the function will be in any other way different from a regular function in Kotlin.

So, if you define a function:

fun Any.print(name: String) { /* ... */ }

You could optionally choose to allow it to be infix only because it has a single argument.

This doesn’t mean that infix functions should be used everywhere. There are specific places that they may be used. One example is where the function by design will always include one argument. Another example is where the function does not have any other varied forms, such as a single-argument form and a multiple-argument form, as that would certainly cause confusion. (just like you mentioned)

Specifically related to the idea I proposed here, which is again just an idea and not a feature in the language, I wanted to be able to convert a function name into a meaningful statement in Kotlin.

My intention was to be able to have a way to define a function:

fun performTaskWhenConditionsAreMet(task, cond)

as such:

outfix fun ``performTask{$task}WhenCondition{$cond}IsMet``()

so, I could call it while the statement looks closer to the English language.

``perform ::mainViewNavigation WhenCondition ::buttonClicked IsMet``()

I know there’s no such thing as an outfix function in Kotlin, but I assume the concept of an outfix function already exists. :stuck_out_tongue:

My question was, what do you mean by “outfix function”? What does it do? What’s the syntax? What’s the purpose of it? I couldn’t make sense of your code example and figure out what an outfix function is, so I’m hoping a definition/explanation will allow me to understand your code and your suggestion.

If parts of a function name were allowed to be separated within double-backticks, then the language would start to look more like a readable text describing a set of rules.

This, I thought, would be a great extension to the language.

I am struggling to see a good use-case for this idea. What would be the purpose of such functions? For your first example, I think it’s a matter of naming to convert it to a normal infix function isAnObjectOfType which in my opinion reads better than a detached word “Object” that could potentially mean an actual Object type. For the second example with a rather complex function, don’t you think it makes more sense to make it more granular split it into more steps, like whenConditionIsMet(::buttonClocked).thenPerform(::mainViewNavigation)? Or a DSL if things get even more complicated.

When the number of function arguments grow, specially at the feature-level where the interface functions are lengthy and specific, then it would be meaningful to use something like this.

Chaining the call is a different approach. What if there was no context with those two functions available in that order?

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.

2 Likes

Yes, complexity would be a concern.

Yeah now that I understand what you want, I have to say I REALLY don’t like this proposal. The double backticks actually harm readability greatly for me, and I think if you get more and more arguments, it’s going to end up looking super messy. The previous answer about creating a DSL seems like the correct option.

2 Likes

I have to disagree with your decision completely but every developer has his own taste.

Having a way to create structures such as this is a dream in any language:

``
    perform ::connectToMediaService
    when { main.isRunning() }
    otherwiseOnConditions *arrayOf(/* ... */)
    attemptToReconnectToMediaChannel audio
    timeout ::contextTimeout

``<...>()

One function, five arguments, reads like instructions in English language, understandable.

I can kind of see the appeal of the idea, but I don’t think Kotlin is a good target language for this. It fits more in languages that already allow heavy self-modification (e.g. lisp-based languages, Forth, Seed7).

Written a little differently:

fun main() {
    perform(::connectToMediaService) whenever 
    { foo() } otherwiseOnConditions 
    arrayOf(/* ... */) attemptToReconnectToMediaChannel 
    audio timeout ::contextTimeout
}

fun connectToMediaService() {}
fun foo() {}
val audio = Audio
object Audio
fun contextTimeout(): Timeout = TODO()
class Timeout

data class Perform(val block: () -> Unit)
fun perform(block: () -> Unit) = Perform(block)
data class Whenever(val perf: Perform, val block: () -> Unit)
infix fun Perform.whenever(block: () -> Unit) = Whenever(this, block)
data class Otherwise(val whenever: Whenever, val conditions: Array<() -> Boolean>)
infix fun Whenever.otherwiseOnConditions(conditions: Array<() -> Boolean>) = Otherwise(this, conditions)
data class Reconnect(val otherwise: Otherwise, val channel: Audio)
infix fun Otherwise.attemptToReconnectToMediaChannel(channel: Audio) = Reconnect(this, channel)
data class WithTimeout(val reconnect: Reconnect, val timeout: () -> Timeout)
infix fun Reconnect.timeout(timeout: () -> Timeout) = WithTimeout(this, timeout)
1 Like

Ok well you didn’t say it would work across multiple lines. :stuck_out_tongue: I agree that it’s not too bad when you put it on multiple lines. HOWEVER
 I also think this is where you start to get into the common coding issue of jamming too much complexity into one function. If you have one function that takes
 what is that, 5 arguments? Maybe it’s too complicated.