class A(private val onUpdate: (value: Int) -> Unit) {
var counter = 0
private set
fun update(newCounter: Int) {
counter = newCounter
onUpdate(counter)
}
}
class View {
fun onClick(action: () -> Unit) { }
}
class B {
private val view = View()
val a: A = A {
view.onClick {
update(it) // works
a.update(it + 1) // compile error `variable a must be initialized`
}
}
fun update(value: Int) {
a.update(value + 1)
}
}
I thought that lambda functions are firstly functions, suprise! and they do not try to access data on initialization but they do. As a workaround I used function inside of B class which does the same but no compiler error.
I think if the workaround is allowed(pass reference of object beeing constructed) then and current error code should work, no?
Probably this doesn’t work because the compiler can not really know if the lambda you are creating there might get called in the constructor of A. If it does a might still be null.
In fact, if you use your little “hack” and the lambda does get called upon initialization you get a NPE:
(Slightly simpler example)
class A(private val onUpdate: (value: Int) -> Unit) {
var counter = 0
private set
init {
onUpdate(10)
}
fun update(newCounter: Int) {
counter = newCounter
onUpdate(counter)
}
}
class B {
val a: A = A {
// a.update(it + 1) also would not work here
update(it + 1)
}
fun update(value: Int) {
a.update(value + 1) // NPE right here
}
}
fun main() {
B()
}
Easiest fix would probably be to make onUpdate an extension function. Then inside onClick you can call this.update() instead of a.update().
My question was about error during compilation, with possible errors in runtime all is clear. Problem - compiler can’t get ‘this’ for inner function, but somehow gets it for method invocation and I am curius why case 1) method invocation works and case 2) accessing property of object does not
Look, to create variable a, you need to give your lambda to the constructor. So, the lambda must be defined before the variable a even exists. Under the hood, the compiler might decompose your code as :
val anonymousLambda = { a.update(it + 1) } // But a does not exist at this point, the compiler does not know what a represents, where it comes from.
val a = A(anonymousLambda);
this is not the same, in my case a is a property of object and it must be accessed only when lamda invoked, some kind of ‘reference’ to the object B already exists and used for initialization of lambda function with method call, but not property
Ok, after analysing this, what I do not understand is why calling directly update(it) from the lambda compiles.
Because, even if the B instance is already available at the time of the lambda creation, B initialisation is not over yet, and as @qwert said, if your lambda is called in A class init code, things will go badly. Note that the lambda must resolve any variable used when it is created, to be sure that when invoked, the linked references will still be available. I think its the reason why the first case goes unnoticed by the compiler : it can solve the this@B reference at this time. But it certainly cannot reference this@B.a, because it does not exist yet.
But, from my point of view, the two options are wrong, and should result in a compile-time error, not just the second case. But I can understand why this case can trick the compiler. Because of the subtility that lies in the fact that B instance exists (primary constructor called) before its initialisation (init {} code and direct property initialisation) is over.
Note that, I think that in such case if there’s a contract that allow to specify that the lambda will not be called in A initialisation, the compiler could infer that the case is fine.
but declaration of val a: A is sufficient for compiler to add information about this property into the class definition, no? (initialization can be in the later init {} blocks)
In theory, yes, you’re right. But in practice, this is dangerous, because the compiler cannot prove that the reference will be valid when the lambda is called (because of the A initialisation code). I think it is the reason the compiler complains.
And in my opinion, that’s the right thing to do (but the error message should give extra-information)
Now, if we compare with Java things become funny. Let’s try to create an equivalent example:
public class B {
public final A a = new A(() -> {
// This does not compile
// a.update();
// This compile, but it can go very wrong if the runnable is called too early
B.this.a.update();
});
public void update() {
a.update();
}
public static class A {
final Runnable action;
public A(Runnable action) {
this.action = action;
// If uncommented, it causes a nasty NPE
// action.run();
}
public void update() {
System.out.println("Update called on A");
}
}
public static void main(String[] args) {
new B();
}
}
Here, calling a.update from the runnable (lambda) also causes a compilation error, but because java does not infer that a is B.this.a. It tries to resolve a reference to a variable at the time, and embed it in a hidden reference.
Now, in java, if you create a runnable that calls this.B.a.update, then things compile, because suddenly, the lambda is a hidden member of B. But, again, things will go wrong if you call the runnable (lambda) in A init code.
So, as the compiler is here to detect errors early, I think it is a good thing to have a compiler error. If we can mitigate it easily (by rewriting one or two lines of code, or by using contracts), that is even better.
Maybe I oversimplify things or miss something important here, but I think the explanation is as simple as:
properties are initialized top to bottom,
all functions are available during initialization,
This is the same in both Kotlin and Java. We can’t reference fields/properties below the current one, but we can invoke a function/method that accesses exactly the sane field. I guess this is a compromise between initialization that is error-proof and flexible to invoke utility functions.
That is what I thought at first sight (see my first answer). However, after looking a little deeper, I’ve realized there’s more than meets the eyes.
The root problem is a cylic dependency between a and the lambda (let’s call it λ) that is passed to its constructor.
But, (and that’s a funny one) there’s B instance between a and λ. Now, naively, I thought that λ was internally referenced as a hidden higher order function, independent of B. This is not the case. The lambda is compiled as a member of B, and therefore, when created, it should only need to know B instance (and it does, as B instance is available upon member initialization).
So, @andrewww is right, if calling this@B.update() from λ is considered valid, then so should be this@B.a.update(). But, allowing this can be dangerous because then, λ can trigger access to a before it is initialized. That’s why I personally think that both statements should be forbidden by default, because the compiler could prove the statement is valid only if it can also prove that it is not called in any initialization code (well, any initialization code before A is properly created).
So, that’s a grey area; I’ve converted the example to Java (see my previous answer), to compare, and Java opts for the “I trust you, verify your code sanity yourself” approach.
Once again, maybe I oversimplify things, but I doubt the compiler even tries to understand circular references here, what is the real order of initialization, etc. My guess would be that it does a very simple thing: it assumes everything inside A {} is positioned at a val a: A = line, so it disallows using a anywhere there. That’s all.
What do you mean? Lambda can’t be compiled into a member, it is always a separate class. Lambda inside A {} is an inner class of B (so it holds a reference to B). Lambda inside onClick {} is an inner class of the first lambda.
Yes, sorry, I used the wrong term, it’s not a member, but an inner class. But the point is : it holds a reference to B. When you decompile it to Java, you will see that update() and a.update() are both decompiled to :
B.this.update(it);
B.this.getA().update(it + 1);
So, strictly speaking, it does not need to know variable a. It only needs to know B instance (which it does, as calling update() directly from the lambda is marked valid), but doing this@B.a is then marked invalid by the compiler, that is why it is a tricky case. Why would one be valid, and not the other ? In both case, you need to ensure you hold a fully initialized B instance (and the compiler seems to stop at the fact that a reference to B exists).
My point is, this case is somewhat complex, and I think the compiler might be improved to at least provide more information about the error. Personnally, I think that calling update() or a.update() should result in either a warning or an error, because they have the same problem: it can potentially use B instance before it is fully initialized. Now, the compiler might even do better, and detect that when the lambda is not used in any init code, then it is safe for it to use B. But that might be difficult to do.
This is not at all about technical possibility or impossibility. From technical perspective both this@B.a and this@B.update() are equally accessible. This is about some simple rules that compiler tries to enforce on us to prevent simple human mistakes.
class Foo {
private val invalid = value + 1 // compile error
private val valid = getValue() + 1 // compiles fine
private val value = 1
fun getValue() = value
}
You can argue that invalid and valid cases are pretty much the same, they represent exactly the same problem and there is no reason to allow one of them and disallow another. But this is how it is.
My english is not very good, so I’ve got trouble explaining, but you’re right, this is exactly the point. However, instead of saying this is how it is, I think we should ask : that is how it is now, is there room for improvement for the future ? What behaviour would be better in the long term ?
Note: I really like your example, it shines in simplicity and still, very clearly show the problem.
I’m not entirely sure what was the reason to design it this way. Not only in Kotlin, but in Java 20 years ago. As said earlier, I suspect this is a compromise. To make the language initialization-proof, we would need to disallow:
reading any non-static props inside constructor,
invoking any non-static functions,
passing this anywhere.
And we need to remember that props initialization (val foo = something) is also part of the constructor code.
This would be very limiting and annoying for developers. Instead, we decided to enforce only some rules where benefits (mitigated risk of human errors) is greater than costs (annoyance). This way the compiler doesn’t provide strict initialization guarantees, but at least it tries to help without getting too much into our way. Sometimes, it still produces false positives as in the OP’s example.
But again, this is only my understanding of this design decision. I may miss something important here.