Call for Feedback: Java Statics, Result Expressions and More
Thank you all for the feedback we got on the previous call! Here comes another round of changes and adjustments. Your opinions and use cases are welcome.
Java Statics and Inheritance
We are going to improve interoperability with Java statics in Kotlin by allowing Kotlin subclasses to access static members of their superclasses: we will now be able to use constants, nested classes and static utilities defined up the inheritance tree. Same for members of companion objects of supertypes.
We will also allow calling Java statics on Java classes that inherit them.
What won’t be allowed is calling an inherited static member on a Kotlin class:
// Java public class Base { public static void foo() {} } // Kotlin class Derived : Base() { fun test() { foo() // OK Base.foo() // OK Derived.foo() // ERROR! } }
Lateinit val’s
With the help of our users we found that we have previously missed an unpleasant hole in the design of lateinit val
: backing fields for such properties were not final, and thus Java code could modify them freely. That makes the val
-ness of such properties vanish, because no code can ever assume any immutability on them, so we decided to take this feature back: from now on, only var
s can be marked lateinit
. We’ll keep thinking on use cases and improvements of this feature.
Backing fields and custom setters
An addition to the previously announced change in backing field syntax: if a property has a custom setter or is open
, the setter may be reading the field before writing it, so there’s no syntax for initializing this property for the first time, unless it’s done upon declaration. So, we now require initializers for such properties:
var foo: Foo? = makeFoo() // initializer required set(v) { if (field != null) notifyListeners() field = v }
If we really need to initialize such a property in the constructor, we’d have to introduce a backing property:
private var _foo: Foo? var foo: Foo? get() = _foo set(v) { if (_foo != null) notifyListeners() _foo = v } init { _foo = ... }
Type parameter declarations
Historically, Kotlin had two forms of syntax for type parameter declarations for functions:
fun <T> T.foo() = ... fun T.foo<T>() = ...
Two is too many, so we decided to keep only the first one, because it places the declaration of T
before its usages, which is easier to read and code completion will be more helpful.
Visibilities of subclasses and elements of declarations
It’s a technical requirement, but rather intuitive: if something is public
, it should not, for example, expose a private
type:
private open class Super class Public : Super() { private class Private fun public(p: Private): Private = ... }
From now on, public classes can’t extend private ones, and public members can’t expose private types.
More formally: a supertype or an element of a declaration must be (effectively) at least as visible as the declaration/class itself.
Marking result expressions
This one is not really decided, and very debatable indeed, but it can’t be added after 1.0, so we have been thinking about it for some time:
As some of you rightfully observed, it may be difficult at times to see what expressions are used as results of blocks or lambdas.
We are considering prefixing such result expressions with the ^
symbol (or maybe some other prefix) to make them visible, in the following cases:
- expression is a result of a multi-line block or lambda, AND
- its type is not
Unit
, norNothing
.
Example (from the Kotlin code base):
val getter = target.getGetter() ?: run { val defaultGetter = DescriptorFactory.createDefaultGetter(target, Annotations.EMPTY) defaultGetter.initialize(target.getType()) ^defaultGetter }
or
private fun transformTryCatchBlocks(methodNode: MethodNode, newTryStartLabels: HashMap<LabelNode, LabelNode>) { methodNode.tryCatchBlocks = methodNode.tryCatchBlocks.map { tcb -> val newTryStartLabel = newTryStartLabels[tcb.start] ^if (newTryStartLabel == null) tcb else TryCatchBlockNode(newTryStartLabel, tcb.end, tcb.handler, tcb.type) } }
Note that simply highlighting result expressions in the IDE is not really a solution:
- It does not prevent accidentally changing results of lambdas or block by adding a harmless-looking
println()
at the end, - It doesn’t work on GitHub, in Terminal or anywhere outside the IDE
We did a quick experiment on the Kotlin codebase, and added all the necessary prefixes:
The diff above will give you a sense of what such code might look like.
To give you an idea of how often this will occur in the code: we got 493 lines changed out of about 230’000 lines of Kotlin code (0.21%), and 233 files were changed out of 2190 Kotlin files we have in our code base (every 10th file).
msgile@gmail.com says:
September 25, 2015= Backing Field Synthetic Name =
I see the niceness of synthetic name of the field, like “this” or “it” but for some reason it feels strange to add another special case here.
= Result Expressions =
“^” is pretty subtle, does not match its use, and I am not seeing the intended symbolism (“Hey lookup?”). How about either a keyword like “yield” or “emit” or a symbol matching closures like “<-“. I hope this is optional, so I only have to use it if I think my code needs it, for safety or clarity.
Andrey Breslav says:
September 25, 2015this
is very different fromit
andfield
, becausethis
a keyword, and the other two are simple automatically defined variables.^
symbolizes an arrow pointing up. Being optional, it’s a lot less useful than if mandatoryVladimir says:
September 25, 2015Andrey, until you explain ^ as “arrow up” I think only about power or XOR). Why not use plain old
return
in such cases? Restrict user to place it always in places of this type. IDE will help to eliminate discomfort from writing looong word. And as you mention – this is very rare case – there will be no big problem without IDE and good compiler error message.Andrey Breslav says:
September 25, 2015return
already has a meaning of “stop the execution of the current function, and return a value”, which is not the intended meaning here.Dmitry Zaytsev says:
September 26, 2015Why not? Isn’t it exactly how it reads: “Execution ends here and X is the result” – same as ^ symbol, if I got it right.
I am looking at it from the perspective of Anonymous classes in Java, which are, despite being a different thing, was often used as substitution for lambdas. Imagine I have Java class (Java 7) which uses some functional interfaces. I would have to use return, since it’s Java. Now, when I will migrate such class to Kotlin it would totally makes sense for me if return statement will stay as is for multi-line blocks.
Andrey Breslav says:
September 27, 2015I don’t think your analogy is correct: anonymous classes (both in Java and Kotlin) have explicit function declarations inside, so there’s no confusion as of where a
return
is returning from. With lambdas that may look exactly like blocks, it is often unclear.Christian says:
September 25, 2015yield or emit sounds good IMO, but why not simply “return”? After all, the expression does return something.
Andrey Breslav says:
September 25, 2015return
means more than just “mark last expression”, it alters control flowclaudiu says:
September 25, 2015^ looks bad. yield or emit more more better.
please don’t make it scala π
Dmitry Zaytsev says:
September 26, 2015This is also a question whether ^ should affect control flow or not. I already see (in my head) code which uses several ^ expressions within a same file without stopping the execution, which then would be arguably harder to follow.
Dmitry Zaytsev says:
September 26, 2015Sorry, within a same block, not file
Sergey Igushkin says:
September 25, 2015+1 for the result expression marks being optional. Maybe IDE should just suggest marking a result expression explicitly if the construct becomes too complex and may be ambiguous.
But if they will be mandatory, then IMO tooling must mark an expression as a result one automatically when having typed a code block and provide a way to move the mark to a new expression below in a few keystrokes without editing too much.
Christian says:
September 25, 2015Please don’t use the “^” prefix, but another one. On the German keyboard for example, pressing ^ on its own does nothing, pressing it twice results in “^^”.
Andrey Breslav says:
September 25, 2015My condolences to those Germans who have to manipulate bits in C/C++ or write manuals with Mac OS X shortcuts in them π
Jokes aside, I see your point.
Jayson says:
September 25, 2015How is
<-
on german keyboards?Alexander Kosenkov says:
October 6, 2015Press ^ and then a space bar
Chris Kent says:
September 25, 2015Result Expressions
I’m not in favour of this. If your code needs this isn’t it a sign that you should probably refactor it and move some of your logic out into a function? I find code that uses lambdas is much more readable when the lambdas are small.
Other languages with similar semantics manage without this, e.g. Ruby, Scala. It’s also pretty ugly.
Jayson says:
September 25, 2015It is not just about readability. It allows the compiler to check that you have not made an error, or have an accidental change you didn’t intend. It would be like implying all
return
and hoping you didn’t make a mistake in your code that is only verified by readability. We would never dropreturn
yet we imply something similar by implyingemit
or^
or<-
Why one, but not the other?Chris Kent says:
September 26, 2015I’m not actually bothered about
return
either. In Ruby and Scalareturn
is optional in function definitions as well as in lambdas. I like that approach and was disappointed Kotlin didn’t adopt it. I’ve never had an issue with it producing code that was hard to read or prone to bugs.That’s why I’m not in favour of this change; it adds a bit of extra complexity and ugliness to the language to solve a problem that I don’t have. And if I did have it I’d solve it with some simple refactoring that would improve my code anyway.
Mohammad Shamsi says:
September 26, 2015Agree with Chris.
I also don’t see the need for “marking result expression”. I think it makes the code uglier and more complex.
If the code is too complicated to understand, it deserves to be extracted to one or more separate methods.
Rodrigo Quesada says:
September 25, 2015“More formally: a supertype or an element of a declaration must be (effectively) at least as visible as the declaration/class itself.”
Why is this a technical requirement? Isn’t defining a public class that extends a private/protected class perfectly valid code in Java? What are you gonna do with the Java interoperability requirement for this cases then?
“…from now on, only vars can be marked lateinit. Weβll keep thinking on use cases and improvements of this feature.”
Are you planning to provide an immutable-like property delegate, so that we can use it with vars then?
Andrey Breslav says:
September 25, 2015It is.
I don’t see an issue here.
You mean something like assign-once? Well, it’s an option.
Rodrigo Quesada says:
September 25, 2015It is.
Then why is it a technical requirement in Kotlin? (just wondering)
I donβt see an issue here.
How would you extend a nested protected abstract static class defined in Java from Kotlin? (not very common, but it happens)
You mean something like assign-once? Well, itβs an option.
Yes. π
Commenting further on the first subject, sometimes you simply don’t want to expose a base class that you only define internally for reusing purposes, so you might want it to be private (or protected).
PS: I know the use cases for this are not so common, so you could probably reply me with some statistics from you code base, but the thing is they do exist and have a justified reason to do so. π
Rodrigo Quesada says:
September 26, 2015Hey, just to remark, when I said “How would you extend a nested protected abstract static class defined in Java from Kotlin?”, I meant to said “How would you extend a nested protected abstract static class defined in Java from Kotlin, exposing the subclass as public?”.
Andrey Breslav says:
September 27, 2015Because it makes things easier to implement, check and enforce.
I would just extend it. I can’t extend it with a public class, but a use case for such a situation is yet to be presented.
If it turns out that this limitation actually makes people’s lives harder, we can lift it. C# people have been living with these limitations for ages, and no one seems to care.
Rodrigo Quesada says:
September 29, 2015Because it makes things easier to implement, check and enforce.
I see, I guess that’s a good reason for taking that decision then, everybody wants you to release 1.0 soon. π
Salomon BRYS says:
September 25, 2015=== Java Statics and Inheritance ===
OK, Great π
=== Lateinit valβs ===
I’m very glad π
=== Backing fields and custom setters ===
OK
=== Type parameter declarations ===
I didn’t even know the second syntax existed :p
=== Visibilities of subclasses and elements of declarations ===
Makes sense π
What about an overriden method that returns a private subclass that extends the type returned by the original method ?
=== Marking result expressions ===
Great, it would make the code more readable.
I agree with msgile when he says that ^ is not expressive enough. I don’t think yield is a great choice because it is not the semantic of yield in many other languages that uses this keyword. I do very much like <- .
Salomon BRYS says:
September 25, 2015Of course, MalePeoples extends Peoples (sorry) :
Andrey Breslav says:
September 27, 2015If the type is less visible than the method, it is not allowed.
Michel says:
September 25, 2015Why not simply use “=” ?
“=” makes more sense than “^” for marking result expressions.
Dale King says:
September 25, 2015Definitely don’t like the ^, it is giving me bad flash backs to lambdas in Objective-C
Igor Lukanin says:
September 25, 2015Marking result expressions with ^ looks extremely ugly and unintuitive. Please stick to some smart keyword. ‘return’ is ok in this case.
Amal Samally says:
September 26, 2015Backing property is a real sadness. I was hoping that in Kotlin we could avoid such ugly code (with different types for getter/setter & field).
And I liked backing field syntax very much. I’ll miss it.
+1 for the result expression marks being optional.
Sergey Igushkin says:
September 27, 2015I was wondering what is the reason for backing property dollar sign syntax removal other than string interpolation incompatibility? That one would be solved by just using
$$property
construct as it seems to me at first glance.Andrey Breslav says:
September 27, 2015Another reason is that it’s just too much syntax for use cases that are extremely rare: about 10 (ten) of them in a >1MLOC github corpus of Kotlin
Dario Elyasy says:
September 27, 2015Marking result expressions:
Very useful indeed!
Why the choice of a prefixed symbol, instead of a separate symbol/word at the beginning of the line? Wouldn’t that (a separate symbol/word) make it easier to searching/editing those marks in the IDE?
Dario Elyasy says:
September 27, 2015Marking result expressions:
also if you make it mandatory, I see the point of all the people that complain about. I am in favor of making it mandatory, but you really need help from the IDE in order not to make this syntax annoying
Alexander Eliseyev says:
September 27, 2015I really like the idea of marking result expressions, it would make code more readable, especially in big lambdas. Although I’m not sure about
^
,<-
would look more appropriate here.Sven Jacobs says:
September 27, 2015lateinit val
While I understand the reason for this change I found
lateinit val
very convenient because I knew that these fields were always set through Dagger dependency injection and I didn’t have to perform null-safety checks on them. Does this mean that I now have to writesomeDependency!!.doSomething()
again for alllateinit var
fields?Sven Jacobs says:
September 27, 2015Couldn’t you allow
lateinit val
with a special compiler flag for people who know what they are doing? πAndrey Breslav says:
September 27, 2015You can always fork the compiler if you want it really badly π Otherwise our policy is: have a language feature for all users or don’t have it at all. Flags are for fine-tuning more low-level things.
Andrey Breslav says:
September 27, 2015Yes, it does, and no, you couldn’t be sure it was necessarily Dagger changing them: any Java code could.
Sven Jacobs says:
September 27, 2015Result expressions
The ^ syntax is not very beautiful π Please keep it as it is or at least make it optional. Just using the
return
keyword would also be ok for me. But regardless what keyword you use, please make this one optional.Jacob Zimmerman says:
September 27, 2015I agree with all but the last thing. For the expressions in a lambda, if it’s more than one line, the return statement should be there to make it clear. This is how java does their lambdas and I think it’s clearest. I’m not sure how it should be done in other expressions (if, when, try), but there’s no good reason to not use return after multiple lines in a lambda.
Andrey Breslav says:
September 27, 2015There is: a lambda looks no different from a block in if/try/when, and there are many functions like
with
whose semantics are block-like too; so, we’d have to require areturn
inif
if we required it in a multiline lambda.Jacob Zimmerman says:
September 28, 2015Huh. Never thought about that…
Well, if you’re going to use a symbol, I’d highly prefer = or :, if possible. I know they’re used elsewhere, so maybe it’s not possible without potentially confusing the compiler.
“out” could also potentially be a decent keyword as it’s already reserved (at least for some uses), it’s short, and it lives up to the meaning pretty well.
Oliver Plohmann says:
September 28, 2015As what the issue with β^β is concerned I would prefer to simply go with “return”. As a former Smalltalk developer I like the Smalltalk-style β^β return character, but I would nevertheless prefer a solution that is transparent for the developer. Is there a need the developer needs to be aware that a sliglty different case applies? If not, free the developer from having to deal with it. I would suggest not to go with yield. It always reminds me of Thread.yield. Also in several other languages yield means to give up the CPU voluntarily. In that way I also found for..yield in Scala confusing at first sight.
Peter Niederwieser says:
September 29, 2015^ feels obscure and not in line with Kotlin’s philosophy. If you think this feature is necessary, you should pay the (keyword) price. With
return
being mandatory for multi-line methods, I wonder why a return(-like) keyword would be considered too heavy-weight for a multi-line lambda.Placing generic type arguments after “fun” is inconsistent with type declarations and function invocations. That’s why I always preferred placing them after the function name. Extension functions with generic receiver (as used in the code example) don’t seem like a good justification; I believe most users will never write such functions.
Jacob Zimmerman says:
September 29, 2015Actually, such functions are the primary reason that I write extension functions. If you make a type that is contravariant but there’s some sort of composition function that requires covariance, the only way to gave that function is as a separate finction, whether extension or not. Usually the extension version is much more fluent. This example has come up in multiple libraries I’ve worked on and seen.
Also placing it after fun is more congruent with how it’s done in Java. Obviously, we don’t necessarily want it to be like Java, but I was able to guess that syntax without ever having to look it up because it was natural to me.
Chris Kent says:
September 30, 2015= Inheritance of statics=
It’s important to note that Java’s handling of statics is inconsistent with itself. Static methods defined on classes can be invoked in subtypes without the class name. But static methods defined in interfaces (introduced in Java 8) must be always be qualified with the name of the defining interface:
Is Kotlin going to inherit this inconsistency from Java? Or is it going to allow interface methods to be invoked without the class name?
I think it would be better to require the class name when calling static methods form supertypes. If you want to invoke the method without using the class name it is possible to explicitly import the method anyway.
Andrey Breslav says:
September 30, 2015We are currently discussing the options regarding superinterfaces. Our user feedback shows that requiring qualified usages of statics is an annoying obstacle for Java interop because of how some popular libraries are designed
Chris Kent says:
September 30, 2015But can’t that easily be fixed with an import? I use a lot of static methods every day, e.g.
Collectors.toList()
,Assert.assertEquals()
andMockito.mock()
. Those methods are almost always statically imported. IDEs make that extremely easy.What’s the problem with importing a static method you want to use without qualifying it? Is it worth adding complexity to the language to avoid a few imports? Then the rules for invoking static methods would be extremely simple and consistent.
Andrey Breslav says:
October 1, 2015Judging by the user feedback, we decided that it is worth it.
Chris Kent says:
October 1, 2015Just out of interest, which libraries are we talking about?
Dirk Dittert says:
October 3, 2015Visibility of subclasses:
Not sure what this is about and why it absolutely has to be prohibited. Shouldn’t it be possible to have a shared base class with internal functionality but return a number of derived public classes from functions?
Result expressions:
PLEASE PLEASE don’t use ^. <- is not really better. It would be really nice if Kotlin did not end up with Scala’s mess of random characters with magic meanings. Why not have a proper keyword like emit or supply for that. It could be optional for short statements (like if it’s the only statement in that scope). I never understood, why it is considered to be more readably to just write the name of a variable to return its value. Especially, as it only has that meaning if it really is the last statement in the control flow.
Statics:/@Chris Kent:
This is my main pain point in Kotlin because I’m doing a lot of TDD. Right now, it’s just not enjoyable to use popular libraries like AssertJ:
There are assertThat methods that are in org.assertj.core.api.StrictAssertions, others come from org.assertj.core.api.Assertions (Assertions is derived from StrictAssertions).
You can only import assertThat statically from one class. You always have to prefix the other one with the correct class. What makes it even worse: you have to know exactly which types are passed to the assertThat() static method. This is because the type of the first parameter determines whether you’re using assertThat from the base class or from the derived class.
This is ugly and a pain while coding, especially because compiler errors for this aren’t really pointing you in the right direction.
Please note: Even if that behavior stays that way, it would be a major relieve if the compiler searched in base classes for suitable static methods to print a helpful error message (something like “static method xy in inheritance hierarchy is not accessable through class abc in Kotlin.”).
I know that static is a Java concept and not really a Kotlin concept. Even if you built a Kotlin facade for that one library, you would only have a solution for one particular case. Sounds like a lot of (unnecessary) busy work to me…
Fred says:
October 10, 2015really like what you guys are doing with Kotlin. The only two cents I’d like to offer is this. I remember watching a video of either Crockford or Stroustrup (can’t remember which) but they were saying how the biggest mistake Ritchie made when creating C was that he didn’t care whether a functions opening brace went at the end of the line or on the next line and so he allowed both.
Thus he forever opened the door for opinions on the matter and devs have been going to war and complaining ever since. So please be decisive. Keep up the good work!
Andrey Breslav says:
October 14, 2015I would love to live in the world where opinions on formatting are eliminated by compilers. However, we feel that building formatting style into a language would be too much of a threshold for adopters
Vladimir says:
October 31, 2015I think that ^ should be replaced by return. And anything after return should be error (unreachable code, or something like that).