More operators to overload

I actually like the fact that you can’t define your own operators willy-nilly (like in Scala), but in my opinion there are too few. Also, adding operators which are not used by the core libs, but which are designated for custom overloads could help to avoid confusion and special cases. E.g. if you use unary plus to “add” something to a scope, you can’t use the mechanism for numbers.

I suggest to add operators systematically around the tilde:

  • unary and binary ~
  • arrows <~, <~> and ~>
  • braces ~[ and ]~
  • assignments / associations :~, :~: ~:

What do you think?

1 Like

AIUI, one of the main reasons for its limited operator overloading, as for so many other little things in Kotlin, is readability.

Kotlin lets you overload the standard operators for mathematical operations, comparisons, collections, ranges, iteration, and destructuring* — but you can’t create your own operators. Neither can you change their precedence.

As a result, overloaded operators should behave in ways that correspond to the built-in ones. (That can’t be enforced in the language, but it does its best to encourage that.) So when you see a * or in or to or […] used on a new type, you already know what to expect.

For example, the book Kotlin In Action illustrates some of these cases with a Point class, a trivial data class holding two co-ordinates. I’m sure you don’t need me to show the code for adding two points together, or multiplying a point by a number, because it’s obvious to everyone exactly how those will work! (Conversely, multiplying two points together doesn’t have a single, obvious meaning — it could be a vector product, or an inner product, or complex multiplication,… — and so the book doesn’t show that case.)

This makes the code readable even to someone who doesn’t know the details of the classes involved.

But what about new operators?

If you were to read someone else’s code, and came across <~>, how would you read it? What would it mean? Clearly those characters have some meaning to you, or you wouldn’t have proposed them; but someone else may have had completely different, conflicting ideas. And that would lead only to confusion, and error.

Kotlin already provides other ways to write concise, domain-specific code when you need to. In particular, non-operator methods can be marked as infix and used in much the same way as operators — except that, being words rather than cryptic symbols, anyone can read them!

For a case in point, look at how Kotlin implements bitwise operators: not as built-in operators, nor even as overloaded operators, but as simple infix functions: and, or, xor, etc. — concise, but also far more obvious and meaningful to most people than obscure symbols.

You clearly have some appreciation of this already. (I feel the same about Scala!) But this isn’t about the number of operators you can overload; it’s about whether those operators already have a meaning in the language. I’m sure the set of operators you suggest would be meaningful in some domain — but to me, I’m afraid they’re just gobbledegook. Are there really no concise words you could use instead? That way, we’d all be able to read them.


(* That’s a fair number, actually. I count about 29 operator functions you can implement — though some of those are used by more than one operator, such as equals() providing both == and !=, so it’s hard to be exact.)

4 Likes

It would mean that the code uses some kind of DSL. Sometimes it would be quite obvious, e.g. if left and right of it would be something looking like a chemical formula, then you know that the symbol denotes the equation of a reversable chemical reaction. And I would argue that in this case there is no really good infix function which would convey the meaning as concise as the double arrow does.

And when we stay in the example of a chemical DSL, there are a lot of existing, meaningful operators we can reuse, such as * and +, but we also have to abuse others, because there is nothing with a similar meaning. E.g. we have to write something like C-O[2], abusing the -. Wouldn’t it be better to have a way to say, “Hey, here comes something which isn’t one of the standard operators” by writing C~O[2] instead?

Here is an example from the Kotlin documentation itself (Type-safe builders | Kotlin Documentation):

fun result() =
    html {
        head {
            title {+"XML encoding with Kotlin"}
        }
        body {
            h1 {+"XML encoding with Kotlin"}
            p  {+"this format can be used as an alternative markup to XML"}
            ...
        }
   }

Would you say here that the + is used according to it’s original “meaning”? Do you see a way to replace it by an infix function? Is it “obvious” what it does? Yes, it could be for you and me, but only because it became a kind of convention to use it for this kind of purpose, not because of the way it is used in the Kotlin standard libraries. And as I mentioned in my first post, you can’t use it when you want to “add” numbers to the scope that way.

And if you have a use case where you need something which doesn’t already have a meaning, you shouldn’t be forced to abuse one which does, which would be just confusing. If my suggested operators stick out like sore thumbs, that is exactly the point: To alert the reader that here goes something on which is different from the normal behavior, that this is its own thing.

I think this was the main point made by @gidds - that operators shouldn’t be specific to domain problems. Otherwise, what if for DSL A we would need <~>, for DSL B we could use <<>> and for C it would be nice to have **? Then we are at Scala again.

But I don’t say we shouldn’t take this route. I don’t have a hard opinion.

I agree with you here. Currently, +x has some kind of conventional meaning in Kotlin, but it feels like an abusive use of operators that got out of control. Which… maybe kind of proofs that custom operator overloading isn’t that bad, because it allows to invent new conventions and patterns.

Anyway, I don’t feel there is anything special in this <~> and other suggested operators. I don’t really know what they could potentially mean. So if allowing them, I would rather allow creating our own operators.

1 Like

I have no idea what +“plus this” does, I would not even know how to look for this in google, it does not make sense to my eye.
It would assume that -“Should also exists”, I have no idea though.

When I try : var foo = +“foo” it does not work.

So for me, this is clear abuse of an operator that does something totally unexpected.

If we cannot review this on GitHub, and we need a IDE to understand code, then we are coding it wrong…

2 Likes

Cause this is a hack. When designing a DSL, we can provide an unaryPlus extension by the scope object to get a syntax like:

jsonArray {
    +"hello"
    +"world"
}

This is an abuse of parameter overloading IMO, but with time it became a kind of convention in Kotlin word. And I believe it all started by Kotlin authors themselves - they did this in kotlinx.html and kotlinx.serialization (above JSON builder - then it was removed).

1 Like

It may have become a convention amongst users of that particular Kotlin library — but it’s nothing I’ve ever seen or used before (and I’ve been doing Kotlin professionally for 7+ years). So it doesn’t seem completely widespread in the Kotlin world.

1 Like

It seems the unaryPlus operator by itself seems like noOp and added for completeness (as opposite to unaryMinus).
However, in mentioned libraries it is used in exactly as one would expect: add the argument to the scope. This is why it only can be used in the scope, it is defined in. And, yes, in that scope you cannot use default operator. This is why unaryPlus is not an opposite to unaryMinus in such scopes.
This is exactly why designing DSL is not just simply override operators, but defining scopes, operators, infix methods, etc. so they all will work together…

About unaryPlus: also note that it has largely been discarded by more recent libraries. For example, Compose uses Text() instead of +.

But part of that is that Compose kinda has automatic +. It’s outside the scope of this thread, but when you call Text, an add operation does happen internally. Thus it’d be a little confusing if + was used, especially since Text has further parameters.

They removed it from jsonArray and they didn’t include it for buildList, etc. They realized there is a problem with a conflict with the unaryPlus member function. This makes this pattern not very useful with numbers and with parameterized types, which greatly limits its use cases.

Along the lines of the subject I have wished for the ability to have extension infix functions which could be called with an implicit receiver of “this”.