Peter Holák

Hello

In an Ideal World: Features I Miss in Kotlin

(Scroll down a bit if you want to get directly to the features list. This is an updated version of the article — thanks for all the feedback to r/Kotlin)

What would it take to implement React in Kotlin, for desktop UI?

This is the question I’ve been trying to answer in the past few days, and you can see some very early results at https://github.com/peterholak/mogul.

You see, every time there’s an internet discussion about any Electron app, people always argue about whether the supposed ease of development justifies the “heaviness” of bundling an entire browser runtime for what might be, at its core, a fairly simple application. By heaviness, they usually mean 3 things

  • disk usage (and thus the download package size)
  • RAM usage
  • CPU usage and overall performance (and all the things it implies, such as battery life on laptops and fans running loud)

I’m not going to get into whether or not people make a bigger deal out of these things than they really are, but the fact of the matter is, if you’re used to React and TypeScript for UI development, there isn’t really anything on the desktop, that can offer a similar experience. Not as far as I know, anyway.

By similar experience, I mean mainly two things:

  • HTML-like flexibility of the UI
  • Writing the UI in terms of render() functions

I might write an article on this in the future, but right now, I want to focus on what my experience with Kotlin as a language has been, while trying to essentially re-create React in Kotlin for the JVM and Kotlin Native.

Overall, I’m pretty impressed, but I found that certain features of the language, or the lack of a few of them, got in the way of how I wanted the code to look.

Before you say anything, I am aware that the design of React is heavily influenced by the abilities and limitations of JavaScript, and rewriting it 1:1 may not be the best idea. The handling of component state and setState in particular is something I'm not too crazy about — many people think the same. But for this initial preview, setState it is.

I see the current “looks exactly like React code” state as just a jumping off point. Especially the “components, elements, instances” structure seems like something that could be improved upon.

If these features were already previously discussed somewhere and rejected, I’d be grateful for a link. Mostly I’m just hoping to find out what people think about these. To my knowledge, I didn’t include any from the big feature survey a few months back.

So without further ado, here they are.

My wishlist, in no particular order

1. Constructor inheritance with a single keyword

Let’s start with something simple.

I want the users of my library to create new components by subclassing Component<Props, State>, just like in React.

Quite frankly, making them write out all the constructor parameters and pass them to the superclass manually is a pain in the ass.

class MyComponent(  
    props: Any,
    children: List<Element>,
    updater: Updater
) : Component<Props, State>(props, children, updater)

Do I really want my users to have to write components like this?

C++ figured this out some time ago and now just allows you to write using Superclass::Superclass; instead of a full-blown constructor re-definition with a parent constructor call.

Something similar would fit nicely into Kotlin as well.

class MyClass : Base inherit

class MyClass inherit constructor : Base

@InheritConstructor
class MyClass : Base  

Probably not the annotation. This would create a primary constructor that takes the same parameters as the parent’s one, and just passes them on. It could also create secondary constructors that match the ones in the base class, but the feature would be pretty useful even without that.

The inherit word could also be something else — any syntax works for me, as long as it’s concise enough and not too cryptic.

Libraries that deal with reflection (or code generation) would probably benefit the most from this feature. Oftentimes, the library calls the constructor internally, and for its users, it’s just an implementation detail.

2. Argument forwarding

From the Kotlin libraries I’ve seen (Anko, TornadoFX, etc.), it is not uncommon to have a PascalCased class and a camelCased DSL function to go with it. A Button class with a button builder function.

It is also not uncommon that the parameters of the function closely follow the constructor parameters of the class. For my specific case, I have the props of a component defined as a data class

data class LayoutBoxProps(  
    val direction: Direction = HorizontalDirection,
    val spacing: Int = 0,
    val style: Style = Style()
)

fun layoutBox(  
    direction: Direction = HorizontalDirection,
    spacing: Int = 0,
    style: Style = Style(),
    childBuilder: KgxBuilder.() -> Unit
)

You see what could be done here. In the JavaScript world, they just use f.apply(this, arguments) in such cases. C++ has something called parameter packs, that allows it to have functions like std::make_unique, which forwards all of its arguments to the appropriate constructor. I believe the Kotlin compiler could help here as well.

If you’re asking why I don’t just pass the LayoutBoxProps object directly to the function, it's because I don't want to have a DSL that looks like this

window {  
    layoutBox(LayoutBoxProps(direction = VerticalDireciton, spacing = 10)) {
        box {}
        box {}
    }
}

How would the syntax look? If we want to cut down on extra typing as much as possible, it would have to be something like

data class LayoutBoxProps(val direction: Direction = HorizontalDirection, val spacing: Int = 0, val style: Style = Style())

fun layoutBox(props: ...LayoutBoxProps, childBuilder: KgxBuilder.() -> Unit) {  
    val nested = KgxBuilder()
    builder(nested)
    children.add(
        Element(layoutBoxType, LayoutBoxProps(...props), nested.children)
    )
}

Maybe a keyword would look nicer instead of the ellipsis. It’s a bit similar to vararg, but different. The * spread operator could be reused for this as well. You would also be able to forward the parameters to any function, not just constructors.

The big benefit of all this is that I can add, remove or change properties in LayoutBoxProps as I need without having to rewrite the function each time.

Recently, I started generating the builders with an annotation processor, which cuts down on the boilerplate, but has its own set of issues (it also doesn’t work with Kotlin Native). Yet I could still use this feature in the processor — I wouldn’t have to worry about parsing the default parameter values, plus it would make the code simpler.

3. Ability to omit the class name when creating objects

Now for arguably the most controversial (and potentially dangerous) one. Instead of writing

val boxStyle = style {  
    border = Borders(
            width = EdgeSizes(top = 2, right = 3, bottom = 3, left = 2),
            color = EdgeColors(top = 0xAAAAAA.color, left = 0xAAAAAA.color)
    )
}

you would be able to write something like

val boxStyle = style {  
    border = [
        width = [top = 2, right = 3, bottom = 3, left = 2],
        color = [top = 0xAAAAAA.color, left = 0xAAAAAA.color]
    ]
}

Another example could be replacing

val points = listOf(  
        Position(10, 10),
        Position(10, 20),
        Position(30, 30)
)

with

val points = listOf<Position>([10, 10], [10, 20], [30, 30])  

The syntax could be different, but you get the idea. It looks neat in these few special cases, but could get pretty bad if someone started using it everywhere.

Still, one of the big draws of Kotlin is not having to write what the compiler can figure out for you. It already has type inference, and this feature would take things a step further.

It’s not a totally uncommon language feature either. C++ has something similar with its list initialization.

#include <vector>
#include <iostream>
#include <string>

class MyClass {  
public:  
    MyClass(std::string name, int id) { }
};

void printData(std::vector<int> v, MyClass my) {  
    for (int number : v) {
        std::cout << number << std::endl;
    }
}

int main() {  
    printData({ 1, 2, 3 }, {"Bob", 5});
    return 0;
}

As you can see, it doubles as a pseudo collection literal syntax in C++.

Even the notoriously feature-averse Go allows you omit the type name in some limited cases (near the bottom of the chapter).

I can see the arguments for and against this in my head, and invariably, they mirror the arguments people use when talking about type inference.

  • “I want to be able to see what’s there! This will decrease code readability!”
  • “But the IDE can just show you! It’s still statically checked! Ambiguous cases still need to be spelled out!”

The case of the DSL

The reason I want this is mainly for use in DSLs. Right now, in my React rip-off, you can set the style either as a function argument, or via its own separate builder.

// Version A: function argument
kgx {  
    box(style = style { width = 10; padding = 10.left and 20.top })
}

// Version B: imperative builder
kgx {  
    box {
        style {
            width = 10
            padding = 10.left and 20.top
        }
    }
}

See that style = style { ... } eyesore in there?

The reason “Version A” even exists is simple: styles can be variables. You can say style = boxStyle, or combine two styles by saying style = boxStyle + redBorders. Or style = boxStyle + style{ width = 100 }.

Wouldn’t a style = [{ width = 10 }] be nicer? It’s a constructor that takes a lambda with a receiver (I won't have a constructor with hundreds of parameters for every style attribute). You might even drop the [] in such a case, just like Kotlin already lets you do with functions.

style = { width = 10 }. Not bad for a DSL.

(I know I could have a fun box(style: StyleBuilder.() -> Unit, ...), but I still need to be able to use styles as variables, so I would also need an overloaded fun box(style: Style, ...), and the same for every element, so it could get messy that way).

The syntax

Now let’s talk about something you’re probably already thinking: “Shouldn’t [] be reserved for collection literals if they ever make it into the language?" Hard to disagree here — when I see [], I immediately think array (or list, or whatever). But:

  • {width = 5} already conflicts with the lambda syntax.
  • (width = 5) I think could work, but I'm still not sure if there won't be issues with another existing or planned feature. It's probably the most obvious choice, since it already looks like before, just with the class name omitted. But if you omit the argument name, (5) already has a different meaning in the language.
  • |width = 5| feels very out of place, and could look like the || operator when nested.

Speaking of collection literals (the most requested feature of the previously mentioned survey), I believe the feature I’m describing is very much in the same spirit. You might think of it as object literals. In JavaScript, you create styles in a pretty similar fashion, by saying const style = { width: 50 }.

The downsides

This feature would only be able to construct concrete classes that are specified in function parameters, so no interfaces, abstract classes or anything of the sort.

If passed into a function that has a lot of overloaded versions, it could potentially slow down compilation, as the compiler has to figure out which version to call (ambiguous cases would obviously be a compilation error).

And like I said in the beginning, it really only makes sense for a fairly narrow set of cases (mostly just DSLs and collections with a lot of newly created elements of the same type). Allowing it in some places (e.g. only for specially annotated classes) and not in others would be pretty weird.

4. Named type parameters with defaults

You know, just

class Component<Props = Unit, State = Nothing?>  

and then you can use

class MyComponent : Component<State = MyState>  

without having to specify the Unit for Props if you have components that don’t have any props (components without either props or state would just inherit Component).

This one doesn’t solve any pressing issue, because the status quo is already good enough, but it makes the code a little nicer and I can’t think of many arguments against it. As far as some other languages go, TypeScript, C++ and Rust have it, Java and C# don’t.

Conclusion

So what do you think? Trying to write a React-like library may not be Kotlin’s most typical use case, but I believe most of these features would be very much in the spirit of Kotlin’s existing design, and would find many other uses.

Naturally, there are other considerations, such as implementation difficulty, compilation speed, possible conflicts with existing or future features — the last thing anyone wants is to have another C++ on our hands.