Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

An introduction context-oriented programming in Kotlin

In this article I will try to describe a new interesting phenomenon which appeared as a by-product of fascinating progress made by Kotlin development team. Namely, the new approach to library and application architecture design, which I call context-oriented programming.

A few words about function resolution

It is well known, that there are three main programming paradigms (pedant's comment: there are other paradigms as well):

...

Functional programming does not bring anything principally new in terms of function resolution. Usually, functional-oriented languages have better scoping rules (pedant's comment: again, not all procedural languages are C, so there are some with good scoping), which allow to perform more fine-grained visibility control for functions based on module system, but overwise, the dispatch is performed based on compile-time types of arguments.

What is this?

In case of object programming, when we call an object method, we have all of its parameters, but additionally we have an explicit (in case of Python) or implicit parameter representing the instance of calling class (here and later all examples are written in Kotlin):

...

In this case there are two implicit this parameters for function doBSomething. One comes form the instance of class B and another one comes form enclosing A instance. The same works for much more common case of lambda closure. The important note about this is that it works not only as an implicit parameter, but also as a scope or context for all functions and objects called in a lexical scope where it is defined. Meaning that method doBSomething in fact has access to any public or private members of A as well as members of B itself.

Here comes Kotlin

Kotlin brings a completely new toy to the playground: extension functions. In Kotlin one can define an extension function like A.doASomething() which could be defined anywhere in the program, not just inside of A. Inside this function one has implicit this parameter called receiver and pointing to the instance of A on which the method is called:

...

The important thing is that B acquires some new behaviors, but only when called in specific parametric lexical context. The member extension function is equal member of class A, which means that the implementation of this function could be different depending on specific instance of A passed as a context. It could even interact with some state of a object.

Context-oriented dispatch

At the beginning of the article I’ve spent some time discussing different function dispatch approaches. The reason behind this preamble is that extension functions in Kotlin allow to define function dispatch in a new way. Now we can say that decision about which function to use is based not only on type of its parameters, but also on the lexical context where it is called. Meaning that the same expression in different contexts could have different meaning. Of course, from implementation point of view, nothing changed, we still have explicit receiver object which governs the dispatch for itself and other objects mentioned in its member extensions, but from point of view of syntax, the approach is different.

...

Effectively combining the behaviors from two classes, but this feature currently is hard to control due to lack of multi-receiver extensions (KT-10468).

The power of explicit coroutines

Surprisingly, one of the best examples of context-oriented approach is already used in the language by coroutine library. The idea itself is explained in detail by Roman Elizarov in his article. Here I would only like to highlight that CoroutineScope is a typical case of context-oriented design with stateful context. CoroutineScope plays two roles:

...

Here doSomeWork is a context-based function defined outside its context. launch methods create two nested contexts which are equivalent to lexical scopes of corresponding functions (in this case both contexts have the same type, so inner context shadows outer one). The good starting point for someone, who wants to start to work with coroutines is the official guide.

DSL

There is a broad class of problems in Kotlin which are usually called DSL problems. Usually, by DSL people mean some code which provides a user-friendly builder to create some kind of complicated internal structure. It is not quite correct to call those builder DSLs since they use basic Kotlin syntax without any tweaks, but let’s stick to common term.

...

Here lexical scope defines a context of its own (a quite logical one, since it represents a section of GUI and its internals) and has access to parent contexts.

What next: multiple receivers

The context-oriented programming provides a lot of tools for Kotlin developer and opens a new way to design code architecture, but do we need something more?

...

Current development in context-driven way is limited by the fact that one needs to define member extension in order to get some context-scoped behavior of class. It is OK, when it is a user-defined class, but what if we need to define some context-scoped behavior for a library class? Or if we want to create extension for already scoped behavior (for example add some extension inside CoroutineScope)? Currently Kotlin does not allow more then one receiver on an extension function. But multiple receivers could be added to language in a non-breaking backward-compatible way. The multiple receivers feature is currently being discussed (KT-10468) and will lead to a KEEP request in nearest future. The problem (or maybe the feature) of nested contexts is that it allows to cover most if not all use-cases of type-classes, another sought-after possible feature. It is quite unprobable that both features will be implemented in the language at the same time.

Addendum

I would like to thank our friendly neighborhood Haskell-loving pedant Alexey Khudyakov for his remarks on the text and correction of my rather unconstrained usage of terms.