While I really like Kotlin in general, I'm not a fan of this feature. It feels like unnecessary syntax sugar for functions that accept multiple parameters.<p>Replacing a function call `foo(bar, baz)` with just `foo()` by bringing `bar` and `baz` into context is not meaningfully better in any way. I'd even argue it reduces the readability of the code due to implicit receivers.
I'm really not a fan of implicit context being passed around like this. I hated Scala's implicit feature although I'm not familiar with the recent apparent improvements referenced in the article.<p>In a larger 'with' block, you cannot tell which invocations will interact with or change the state of a context object without looking at site of the definition of the function.<p>For me, when reading others' code, being able to construct a mental model of what the effects of a line of code in isolation is a vital property for code to be readable/comprehensible. Without implicits, you can mentally constrain what effects a line like "x.y(z)" will have on the state of the program.<p>One issue I had with the Scala feature was how it made refactoring is more fraught - as it's not clear which pieces of code in the 'with' block can be moved elsewhere from just reading the code.<p>Context objects behave like a form of global variable except their use can be even more confusing as Foo@this will refer to different things depending on the runtime call stack. And similarly, it bakes in a singleton aspect to the context objects. The example model from the article should allow functions to operate on multiple Transaction or AccountRepo instances, for example.
Context receivers are also being considered as a poor man's DI/insane man's DI, depending on how you see it. Probably both. After all, if you have a class that requires A, B and C to do its work, marking it as needing those three in context make it super easy to just always have to provide them.<p>Unfortunately (fortunately? Because I know the evil, disgusting things I would do if it was easier), right now you have to nest with(x) or x.apply { } blocks to have everything in scope. A with(a, b, c) { } syntax would be very welcome, but I don't think functions of type (A, B, C).() -> Unit are going to exist any time soon.
It seems really confusing that the context object becomes an implicit `this`. It is already hard enough to understand what scope a name will be looked up in. Many languages suffer from this when they have a kind of `with` block. Ironically, VB6 gets it right. There you have to qualify members with a leading dot, if they are to be looked up in the context, e.g.:<p><pre><code> ' Haven't written this in 20 years so maybe it is not 100% correct :-)
With myForm
.Title = "Hello World"
.Maximize()
End With
</code></pre>
When I heard "context receivers", I thought something completely different and got excited: Sometimes you need to pass a parameter to a function that only gets called several levels deep. It is ugly to add that parameter to every intermediate function. But it is even worse to add a global variable. The OOP solution would be to put all the functions as methods on an object, and pass the data via a field on the object. But what if you could pass the parameter like this (pseudocode):<p><pre><code> function outer() {
use-extra-context(logcolor="RED") {
foo();
}
}
function foo() { bar(); }
function bar() { baz(); }
function inner() takes-extra-context(logcolor) {
print("Something", color=logcolor);
}
</code></pre>
Basically something like dynamical scope, or thread local storage. (Or reading from the other comments, maybe this proposal does just that and I missed something?)
Seems the examples are contrived.<p>* Transactions are first-class in most RDMBS, performing I/O in between each one, and then sending a commit or rollback seems bizarre when you can send off one, not to mention implementing it in the host language is unnecessary.<p>* It seems to be using blocking I/O when most platforms have evolved to concurrent I/O.<p>* If the host language does indeed expose high level start/commit/rollback, it should be exposed minimally with a RAII-safe callback routine, something like `runTransaction`. You wouldn't want to see the former in the wild.<p>* It seems to argue for the use of context / implicit parameters, where I think you really don't want to use it in this case. Implicits or Context seems better for something like executors or React Context.<p>* Are they really using floats for monetary value?
A lot of these sorts of language features feel to me like workarounds for a lack of methods with multiple dispatch: methods like the transfer method seem to belong to a collection of classes but typical OO languages force you to pick a single class to attach them to.
Useful for things like React Context when using Kotlin/JS.<p>The other way to look at this is this lets you model your functions dependencies into two different groups:
1/ "Data" your functions is working with - passed as good ol' arguments
2/ "Services" your function needs to do certain operations - passed as context<p>I guess it's slightly nicer when thought that way, though good luck getting a team of 200 developers on the same page regarding how to use these advanced features.<p>Code review:<p><pre><code> A: "Why are you passing that data as context?"
B: "makes it cleaner"
</code></pre>
Cleanliness lies in the eyes of beholder, so how do you argue with that?
Can someone provide a decent, frequent example that merits adding all this overhead to a language? I'm having a very difficult time thinking why use this instead of plain interfaces and extension methods. Heck is just doing `Service.transfer(trx, args)` really such an abomination that needs correction?