Problems with Kotlin(kukuruku.co)
kukuruku.co
Problems with Kotlin
https://kukuruku.co/post/why-kotlin-sucks/
163 comments
> Assignment is not an expression. We've written 20K of production Kotlin code without this ever being a useful convention.
Out of curiosity, what is the idiomatic way of writing an input loop? The C/Java way would be something like
I think the author is referring to the ternary operator, not the elvis operator.
Out of curiosity, what is the idiomatic way of writing an input loop? The C/Java way would be something like
int bytesRead = 0;
while ((bytesRead = in.read(buf)) != -1) { // ...
> The ?: operator is great when chaining a bunch of operations all of which could return a null which we want to convert to a default. Here is an example: claimData?.claimTimeMinutes?.toLong() ?: 5LI think the author is referring to the ternary operator, not the elvis operator.
Most likely:
I've yet to write an input loop in Kotlin.
in.buffered.readText()
The Kotlin stdlib has a rich set of extension methods for reading in whole files of various common formats at once. readText() reads it all into memory at once. readLines() reads it into a list of Lines. forEachLine{} runs a closure on each line. useLines reads it into a sequence (a lazily-evaluated collection) and then passes that into a closure. buffered converts it to a BufferedReader, basically wrapping it in your buffering loop. inputStream converts it to an InputStream, reader to a Reader, and most common Java protocol libraries take one of those two, so if you need something more complicated you just pass that in.I've yet to write an input loop in Kotlin.
I have no experience in kotlin but many other languages that use iterators would go with something like
for length, bytesRead in input.bytes() { //...
Edit: quick google search, the actual syntax is file.forEachBlock { bytes, size -> //...> Assignment is not an expression. We've written 20K of production Kotlin code without this ever being a useful convention. Assignment being an expresssion is layover from the C family of languages. If you really want C's syntax and semantics, go write C.
Python doesn't treat assignment as an expression. E.g. I can't do:
Python doesn't treat assignment as an expression. E.g. I can't do:
if a = 1:
pass
or def c():
return a = 1
or: a = lambda c: c.x = 1
But I can still do: a = b = c = 2
to set multiple variables at the same time.The comparison operators are also interesting:
if not 10 < x < 20:
raise ValueError("Out of range.")
Not every language allow this.That looks rather inconsistent to me. Isn't the last expression equivalent to a = (b = (c = 2)), and if not why?
I guess it is syntax sugar for:
c = 2
b = c
a = bWhat warrants this special-case sugar though? Is it somehow super convenient in python?
I mostly write C code and while it features these chained affectations (as a side effect of assignations being expressions) I never use them. I also seldom encounter them in the wild. Seems odd to me that a "lean and clean" language like python would bother special casing it.
I mostly write C code and while it features these chained affectations (as a side effect of assignations being expressions) I never use them. I also seldom encounter them in the wild. Seems odd to me that a "lean and clean" language like python would bother special casing it.
Python has a bunch of special cases like this that make the code more readable (according to it's creators):
`x not in xs` --> `not (x in xs)`
`0 < x < 10` --> `0 < x and x < 10`
`x = y = 5` --> (expanded above)
`in` is used both in `for...in...:` and `x in xs`
I feel like Python will gladly sacrifice being 'small and lean' if it leads to gains in readability, although they don't introduce these willy-nilly. Obviously, a lot of it is subjective, but I happen to mostly like it.
I feel like Python will gladly sacrifice being 'small and lean' if it leads to gains in readability, although they don't introduce these willy-nilly. Obviously, a lot of it is subjective, but I happen to mostly like it.
You may never use them but it is used often enough that they included it as syntactic sugar.
You're overcomplicating it, think of it from a math perspective. Don't just look at it as the "assignment operator", look at it as an equals sign.
a=b=c=2. They're all equal, all variables here equal 2.
a=b=c=2. They're all equal, all variables here equal 2.
I don't get what maths have to do with it.
In maths a = b = 2 is the same as a = (b = 2), (a = b) = 2, (a = b = 2) or a = 2 = b. Clearly it doesn't apply here.
In maths a = b = 2 is the same as a = (b = 2), (a = b) = 2, (a = b = 2) or a = 2 = b. Clearly it doesn't apply here.
From a "math perspective," the equals sign is a statement, not an expression. It is asserting the state of something. In programming, we're not 'stating' that `a = b = c = 2`, we are performing an operation to make it happen. This is why in some early languages the assignment operator was `:=` and the comparison operator was `=`.
I think it's a joke.
https://en.wikipedia.org/wiki/Poe's_law
I'm a huge fan of Kotlin. I think the author just doesn't understand the niche it hits.
My team has a significant investment in the JVM. I despise working in Java because of how heavy it is to write. I like Scala, but the team I'm on finds it too complicated. Clojure is out because nobody on the team wants to write lisp. And Groovy doesn't have types. We looked at Ceylon, too, but at the time we hit a lot of compiler issues (if I remember correctly).
So where does that leave us? There are likely other languages we could choose that target the JVM, but the pragmatist in me says I don't want to stray too far off the beaten path for this.
Is Kotlin perfect? No. But is it a better developer experience than Java? Oh hell yes.
My team has a significant investment in the JVM. I despise working in Java because of how heavy it is to write. I like Scala, but the team I'm on finds it too complicated. Clojure is out because nobody on the team wants to write lisp. And Groovy doesn't have types. We looked at Ceylon, too, but at the time we hit a lot of compiler issues (if I remember correctly).
So where does that leave us? There are likely other languages we could choose that target the JVM, but the pragmatist in me says I don't want to stray too far off the beaten path for this.
Is Kotlin perfect? No. But is it a better developer experience than Java? Oh hell yes.
For what it's worth, Groovy does have types. It is not dynamically typed, but optionally typed. Throw a @CompileStatic annotation on your classes (or use a global withConfig) and it is very nicely typed, using Java's type system.
> Throw a @CompileStatic annotation on your classes
Only if your site has upgraded to version 2.x of Apache Groovy, which many sites haven't. Even then, converting everything to @CompileStatic at once only works wholesale for tiny codebases. You'll face a lot of debugging for anything larger -- it's more like "throw" a @CompileStatic onto one class at a time, and test after each. You'll hit the wall soon enough. Consider why Groovy itself is still written in Java, not in statically compiled Groovy, and there's certainly no mention of converting its codebase anytime soon in the latest roadmap posted on their developers mailing list a day ago.
And of course Groovy, unlike Kotlin, only triggers IntelliJ's (and Android Studio's) code completion perhaps 80% of the time. Kotlin was created by the same people who built IntelliJ and for the explicit purpose of working properly with it 100% of the time.
Only if your site has upgraded to version 2.x of Apache Groovy, which many sites haven't. Even then, converting everything to @CompileStatic at once only works wholesale for tiny codebases. You'll face a lot of debugging for anything larger -- it's more like "throw" a @CompileStatic onto one class at a time, and test after each. You'll hit the wall soon enough. Consider why Groovy itself is still written in Java, not in statically compiled Groovy, and there's certainly no mention of converting its codebase anytime soon in the latest roadmap posted on their developers mailing list a day ago.
And of course Groovy, unlike Kotlin, only triggers IntelliJ's (and Android Studio's) code completion perhaps 80% of the time. Kotlin was created by the same people who built IntelliJ and for the explicit purpose of working properly with it 100% of the time.
What did you struggle with in Scala? Anything you could write in Kotlin you can write in Scala with the same structure. Scala has some extra constructs that can save you a lot of code a lot of the time, but you don't have to use them if you don't want to. There are horribly incomprehensible libraries available in Scala and there aren't any for Kotlin yet, but I think that's just a function of the fact that Kotlin's so much less mature all-round, and again you don't have to use them.
The "just don't use those parts of language" argument was also used a lot for C++. I just don't buy it.
You are not in sole control of your code base. You share it with your colleagues who are on different skill levels. You are drawn into community gathered around the language and you need to use, debug, and sometimes contribute to other people's code. Just fencing off some common language features isn't going to cut it. It's better to select tools that are well suited to problem at hand, at right level of abstraction and of manageable complexity to your team. Even if that means less features and more ground work.
You are not in sole control of your code base. You share it with your colleagues who are on different skill levels. You are drawn into community gathered around the language and you need to use, debug, and sometimes contribute to other people's code. Just fencing off some common language features isn't going to cut it. It's better to select tools that are well suited to problem at hand, at right level of abstraction and of manageable complexity to your team. Even if that means less features and more ground work.
I agree with you as far as C++ goes. I think the difference is that the things Scala adds respect the rest of the rules of the language.
If you introduce exceptions in C++, everyone working on any line of the codebase needs to understand exception-safety. If you introduce RAII you have to enforce it on every constructor, and since constructors can fail you have to introduce exceptions. If you introduce templates you have to use RAII for all your types in case they're used in a template.
In Scala if you use a higher-kinded type it works exactly as you'd expect (indeed I'd argue that the absence of higher-kinded types is equally confusing - why can't I use List as a type parameter in Kotlin?) - a method or type that uses HKT acts like any other method or type. If you have a function whose implementation uses a pattern match it might make it confusing to read that function, but to any caller from outside that function still conforms to its signature and still makes sense. If your colleague writes a Profunctor instance for one of their types and litters your code with ^>> calls that's probably annoying (and should probably fail code review), but ultimately it's just a plain old function call that you can click through to in your IDE and see what it's doing and whether it makes sense.
If you introduce exceptions in C++, everyone working on any line of the codebase needs to understand exception-safety. If you introduce RAII you have to enforce it on every constructor, and since constructors can fail you have to introduce exceptions. If you introduce templates you have to use RAII for all your types in case they're used in a template.
In Scala if you use a higher-kinded type it works exactly as you'd expect (indeed I'd argue that the absence of higher-kinded types is equally confusing - why can't I use List as a type parameter in Kotlin?) - a method or type that uses HKT acts like any other method or type. If you have a function whose implementation uses a pattern match it might make it confusing to read that function, but to any caller from outside that function still conforms to its signature and still makes sense. If your colleague writes a Profunctor instance for one of their types and litters your code with ^>> calls that's probably annoying (and should probably fail code review), but ultimately it's just a plain old function call that you can click through to in your IDE and see what it's doing and whether it makes sense.
I hear you.
In my experience with quite a few Scala project with mixed teams it's not a problem though.
You just maintain some shared culture of "don't do X if not absolutely necessary". You need that for any language. In this case it's: don't do typelevel tricks if you can help it. And it turns out that 99.9 % of non-framework code doesn't need it. And the large majority of devs are fine with writing clear code.
Then there are some smart devs that are at a stage of personal development where they are intrigued with typelevel tricks. I've only had one colleague who didn't wasn't open to the "hey, that's not really necessary there. Let's not add complexity if we can help it" type of code review comment.
To each his own. But for the teams I've worked with Scala was the typed-FP-on-the-JVM lets-get-shit-done language.
In my experience with quite a few Scala project with mixed teams it's not a problem though.
You just maintain some shared culture of "don't do X if not absolutely necessary". You need that for any language. In this case it's: don't do typelevel tricks if you can help it. And it turns out that 99.9 % of non-framework code doesn't need it. And the large majority of devs are fine with writing clear code.
Then there are some smart devs that are at a stage of personal development where they are intrigued with typelevel tricks. I've only had one colleague who didn't wasn't open to the "hey, that's not really necessary there. Let's not add complexity if we can help it" type of code review comment.
To each his own. But for the teams I've worked with Scala was the typed-FP-on-the-JVM lets-get-shit-done language.
Don't confuse the argument of "you don't have to use language features you don't understand" with "certain language features are heavily restricted, if not verboten, in many organizations'/projects' style guidelines." Certainly, many programmers don't understand variance, type classes, and other language tools Scala either has direct support for or is powerful enough to implement and use in a simple and clean fashion. Those advanced tools are often used to reject errors at compile time rather than run time and implement certain types of optimizations without sacrificing safety, code maintainability, or legibility.
Again, using Scala's advanced features isn't akin to something like using raw pointers in C++. I find Scala's collection library a good example of that - programmers of all levels can use Scala collections without understanding all the type-level tech they're implemented with.
Again, using Scala's advanced features isn't akin to something like using raw pointers in C++. I find Scala's collection library a good example of that - programmers of all levels can use Scala collections without understanding all the type-level tech they're implemented with.
> I find Scala's collection library a good example of that - programmers of all levels can use Scala collections without understanding all the type-level tech they're implemented with.
That was my reason to dislike Scala. It doesn't work for me. I must be able to dig into any library code I use. And when I tried to dig into Scala collections library, I ran away screaming in horror. When I tried to implement my own collection based on linked list, I wasn't able to do it. I was able to learn Java collections easily, they are really easy to use and understand. You just open Collection.java, List.java, ArrayList.java and read it, it's easy. It might be hard for something like ConcurrentSkipList, but this structure is hard, it's okay. But when I tried to read Scala's collection in the same way, I drown in traits.
May be there are people who can use libraries purely from interface and documentation perspective. I'm implementation guy, when I have a question, first thing I'm doing is investigating implementation code. And if implementation details are buried below kilometers of abstraction, I don't like it.
That was my reason to dislike Scala. It doesn't work for me. I must be able to dig into any library code I use. And when I tried to dig into Scala collections library, I ran away screaming in horror. When I tried to implement my own collection based on linked list, I wasn't able to do it. I was able to learn Java collections easily, they are really easy to use and understand. You just open Collection.java, List.java, ArrayList.java and read it, it's easy. It might be hard for something like ConcurrentSkipList, but this structure is hard, it's okay. But when I tried to read Scala's collection in the same way, I drown in traits.
May be there are people who can use libraries purely from interface and documentation perspective. I'm implementation guy, when I have a question, first thing I'm doing is investigating implementation code. And if implementation details are buried below kilometers of abstraction, I don't like it.
The Scala collections library has a fair bit of second-system effect and plain bad design. It's being rewritten with simplification as an explicit goal (even at the cost of sacrificing some expressiveness) for the next major version of Scala. In the meantime there are a couple of alternative libraries, and fundamentally the standard one does work. (If you need to implement a custom collection the recommendation is to only implement an interface - or maybe even just a suitable typeclass - and not try and reuse any implementation traits/base classes from the standard library).
Which is to say, I don't blame you, but FWIW collections are a known-bad part of the language and exemplify a lot of what the community wants to move away from. I find Scala is great for being able to read the implementation of things, particularly in terms of how much the frameworks tend to be written in plain old Scala code rather than magic annotations or what have you (e.g. in Spray/akka-http, all the directives you use to define your web routes are just ordinary Scala functions and you can click through and read their definitions).
Which is to say, I don't blame you, but FWIW collections are a known-bad part of the language and exemplify a lot of what the community wants to move away from. I find Scala is great for being able to read the implementation of things, particularly in terms of how much the frameworks tend to be written in plain old Scala code rather than magic annotations or what have you (e.g. in Spray/akka-http, all the directives you use to define your web routes are just ordinary Scala functions and you can click through and read their definitions).
I like Scala. I've written a lot of Scala. But choosing a language is a team decision. And Scala draws the same kind of ire as C++. It's to the point that I have seen seasoned engineers laugh any time Scala is suggested.
To provide a bit more depth, a large number of the developers I work with (folks I respect) don't _want_ the kitchen sink. They see it as dangerous. Even if _they_ can handle it, they know that there are a lot of people who can't. Sure, you can limit the features you use via convention, but that is shifting a tech problem to being a people problem -- you usually want to go the other direction.
You can "yeah, but..." all you want, but it's a valid, reasonable opinion. It's subjective, but that's just the way of it.
To provide a bit more depth, a large number of the developers I work with (folks I respect) don't _want_ the kitchen sink. They see it as dangerous. Even if _they_ can handle it, they know that there are a lot of people who can't. Sure, you can limit the features you use via convention, but that is shifting a tech problem to being a people problem -- you usually want to go the other direction.
You can "yeah, but..." all you want, but it's a valid, reasonable opinion. It's subjective, but that's just the way of it.
I was part of a startup where I wrote all our server stuff in Scala. It was just three of us to start and I was the only dev. As we added more people, no one wanted to touch the Scala code.
I tried everything. I made some nice Java interfaces so they could write plugins; exposed all the data they needed, wrote a bunch of documentation, had a learning session. No one contributed a damn thing.
They started firing people after I had already left for Australia and was working remotely. They kept me on while secretly having a former Amazon person rewrite my entire service in Java. Good thing I explicitly asked for the whole thing to be kept GPL prior to the contract.
Last year I got hired into a team that does the majority of their work in Scala. It's been a great experience.
I tried everything. I made some nice Java interfaces so they could write plugins; exposed all the data they needed, wrote a bunch of documentation, had a learning session. No one contributed a damn thing.
They started firing people after I had already left for Australia and was working remotely. They kept me on while secretly having a former Amazon person rewrite my entire service in Java. Good thing I explicitly asked for the whole thing to be kept GPL prior to the contract.
Last year I got hired into a team that does the majority of their work in Scala. It's been a great experience.
> I think that's just a function of the fact that Kotlin's so much less mature all-round
You mean "old", not "mature". Scala is old and still pretty immature for its age
The Kotlin tooling and IDE support is certainly already way ahead of Scala's after just a year in existence in robustness and functionalities. And I'd rank the two compilers at about the same level in terms of maturity (Kotlin's is much faster than Scala's though).
You mean "old", not "mature". Scala is old and still pretty immature for its age
The Kotlin tooling and IDE support is certainly already way ahead of Scala's after just a year in existence in robustness and functionalities. And I'd rank the two compilers at about the same level in terms of maturity (Kotlin's is much faster than Scala's though).
The post hits a lot of real pain point when working with Kotlin.
In particular, nullable types seem like a good idea (I thought so as well) but in practice are a real PITA. That's most acute when defining fields, where non-nullable fields have to be initialized directly.
JetBrains themselves realized it was painful, and so we have `lateinit` to define mutable non-nullable fields that can be initialized later. But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here.
I must now have spent an order of magnitude time dealing with the initialization of non-nullable fields than I ever did tracking null pointer exceptions.
That being said, Kotlin is still a net improvement over coding in Java. In fact, if you're going for a large codebases, I think it's one of the most tractable languages out there (C# is a strong contender as well).
In particular, nullable types seem like a good idea (I thought so as well) but in practice are a real PITA. That's most acute when defining fields, where non-nullable fields have to be initialized directly.
JetBrains themselves realized it was painful, and so we have `lateinit` to define mutable non-nullable fields that can be initialized later. But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here.
I must now have spent an order of magnitude time dealing with the initialization of non-nullable fields than I ever did tracking null pointer exceptions.
That being said, Kotlin is still a net improvement over coding in Java. In fact, if you're going for a large codebases, I think it's one of the most tractable languages out there (C# is a strong contender as well).
> In particular, nullable types seem like a good idea (I thought so as well) but in practice are a real PITA. That's most acute when defining fields, where non-nullable fields have to be initialized directly.
> JetBrains themselves realized it was painful, and so we have `lateinit` to define mutable non-nullable fields that can be initialized later. But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here.
I'm curious, where have you found the need for lateinit? Forcing initialization of non-nullable types has always seemed like a great idea to me, and learning to deal with it in Kotlin has made picking up Rust a lot easier.
I'm curious, where have you found the need for lateinit? Forcing initialization of non-nullable types has always seemed like a great idea to me, and learning to deal with it in Kotlin has made picking up Rust a lot easier.
I use it a lot in Android, where certain objects are created by the framework, and I don't want a nullable reference for one of its properties.
JSON and reflection, you know you gonna get a value once you'll have instantiated your object from a JSON description. So my var are lateninit because I don't need a ? or !!
[deleted]
You can use constructors for JSON now. See https://github.com/FasterXML/jackson-module-kotlin
Initialize them with default values, then? Or if you're using Jackson there is a module from FasterXML that provides support for Kotlin data classes.
lateinit is a crutch to get things working, not something you want to use long-term.
lateinit is a crutch to get things working, not something you want to use long-term.
Lateinit is good for what it is designed for which is allowing for fields that are always going to be initialized before use but after construction, normally by a library. I wouldn't describe it as a crutch in that case.
It'd be ideal if libraries were better about using constructor injection when possible, but I think there are some valid scenarios where circular dependencies make it impossible to use only constructor injection.
It'd be ideal if libraries were better about using constructor injection when possible, but I think there are some valid scenarios where circular dependencies make it impossible to use only constructor injection.
> It'd be ideal if libraries were better about using constructor injection when possible, but I think there are some valid scenarios where circular dependencies make it impossible to use only constructor injection.
Thankfully it is rare to require circular dependencies, but this is probably the one use case where lateinit is certainly warranted.
Thankfully it is rare to require circular dependencies, but this is probably the one use case where lateinit is certainly warranted.
lateinit is bad, because it forces you to use var. If you're using var, you can change this value later and you want the language to prevent that (unless you want a mutable property, of course).
Never really thought about it, didn't even realise it was tied to Jetbrain and not build into Kotlin itself. I feel initialise a default value counter intuitive, what the default of a string , "", "default", "test". I know it works because it'll be reassigned with the JSON value later, but still feel dirty.
I prefer the Swift way let value: String!
Why not use Jackson?
I define the bulk of my JSON datatypes as:
For circular structures & injected values, I make them 'var' and inject after construction. This is a bit icky but generally manageable; if I get too uncomfortable with it, I can move the property declaration to the body, make it private set, and provide an accessor to handle all the injection at once.
I define the bulk of my JSON datatypes as:
data class Foo(
val bar: String,
val baz: String,
val quux: Int
)
No nullables needed, unless the actual JSON spec calls for a nullable field, in which case I want the compiler to remind me to handle the null case. Jackson handles everything out-of-the-box; it has no problem with constructor injection, and requires no annotations for the default case.For circular structures & injected values, I make them 'var' and inject after construction. This is a bit icky but generally manageable; if I get too uncomfortable with it, I can move the property declaration to the body, make it private set, and provide an accessor to handle all the injection at once.
> I'm curious, where have you found the need for lateinit?
It's useful as soon as a field is managed by a container, which means it will most likely not be initialized at construction time of the object.
Examples of containers: Android, JavaEE, web servlet containers, DI frameworks, etc...
So basically, they are pretty much ubiquitous.
It's useful as soon as a field is managed by a container, which means it will most likely not be initialized at construction time of the object.
Examples of containers: Android, JavaEE, web servlet containers, DI frameworks, etc...
So basically, they are pretty much ubiquitous.
> Android, JavaEE, web servlet containers, DI frameworks, etc...
Most of which support constructor injection.
Most of which support constructor injection.
Exactly. I spent some time testing out converting a small part of our large codebase that uses Spring to Kotlin and was able to easily switch over to constructor injection. I think it actually made the injection cleaner. It was nicer for my tests too, instead of @Mock/@InjectMocks magic.
If you use Android along with dependency injection (Dagger2). This was at least necessary when Kotlin was close to 1.0.
This was the only part where I ever used lateinit.
This was the only part where I ever used lateinit.
Dagger supports constructor injection, why would lateinit be necessary?
Many of the classes you'd write as an Android developer are instantiated by the system somewhere using only those constructors that the system expects to exist. Views, for example, are typically instantiated based on XML layout files that simply reference views by name, without a way to specify a specific constructor to use.
Ah, yes - layout inflation is such fun. Still, there are libraries you can use that don't require usage of lateinit. Kotterknife [1] is an option, the Kotlin android extensions [2] is another option though relying on code generation it may not be the first choice of people. Delegates in Kotlin are a powerful tool, and a great way to handle cases where you think lateinit is necessary but really isn't.
Otherwise, if you're going with Kotlin anyway you can just get rid of layout XML altogether with anko [3] or even give MVVM a shot with KBinding [4].
[1]: https://github.com/JakeWharton/kotterknife [2]: https://kotlinlang.org/docs/tutorials/android-plugin.html [3]: https://github.com/Kotlin/anko [4]: https://github.com/BennyWang/KBinding
Otherwise, if you're going with Kotlin anyway you can just get rid of layout XML altogether with anko [3] or even give MVVM a shot with KBinding [4].
[1]: https://github.com/JakeWharton/kotterknife [2]: https://kotlinlang.org/docs/tutorials/android-plugin.html [3]: https://github.com/Kotlin/anko [4]: https://github.com/BennyWang/KBinding
I haven't seriously tried Kotterknife, anko (been meaning to!), or KBinding (hadn't heard of it before, will check it out) -- I'm currently enjoying the Kotlin android extensions for referencing views inside custom viewgroups.
I use field injection to pull in instantiated Picasso/Glide image libraries and to create/bind to persistent view presenters (it's a Mortar/Flow based app currently). I'm not sure if this is best practice or what but I feel reasonably comfortable having image loaders in the view (when those image loaders do their real work off the UI thread of course).
Speaking of Kotterknife, I'm actually in the process of migrating away from Butterknife as I slowly port the app over to Kotlin. Butterknife is immensely useful for Java-based apps, IMO, but the Kotlin android extensions provide most of what I need and Kotlin's syntax largely takes care of the boilerplate I was avoiding by going with BK in the first place. I do miss string/integer/other binding but it's not so bad and, IIRC, kotterknife doesn't provide that anyway -- I explored adding it [1] but I didn't enjoy writing all that code and ended up giving up.
I forgot to mention another use-case for lateinit: junit tests. I use lateinit in the test class when testing code that uses constructor injection. This is probably less controversial because it is just tests, though.
[1] https://github.com/dpkirchner/kotterknife/commit/39dda1965ee...
I use field injection to pull in instantiated Picasso/Glide image libraries and to create/bind to persistent view presenters (it's a Mortar/Flow based app currently). I'm not sure if this is best practice or what but I feel reasonably comfortable having image loaders in the view (when those image loaders do their real work off the UI thread of course).
Speaking of Kotterknife, I'm actually in the process of migrating away from Butterknife as I slowly port the app over to Kotlin. Butterknife is immensely useful for Java-based apps, IMO, but the Kotlin android extensions provide most of what I need and Kotlin's syntax largely takes care of the boilerplate I was avoiding by going with BK in the first place. I do miss string/integer/other binding but it's not so bad and, IIRC, kotterknife doesn't provide that anyway -- I explored adding it [1] but I didn't enjoy writing all that code and ended up giving up.
I forgot to mention another use-case for lateinit: junit tests. I use lateinit in the test class when testing code that uses constructor injection. This is probably less controversial because it is just tests, though.
[1] https://github.com/dpkirchner/kotterknife/commit/39dda1965ee...
>But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here.
If you need a run-time check to figure out whether a supposedly non-nullable field has been initialized, that sounds like a code smell to me.
I see the fact that something can only be initialized late as an internal matter of a library/module/part of project/etc. that should never leak outside of it (i.e. the outside world should never have to deal with potentially uninitialized objects - if it has to, then the field is not really non-nullable).
If you need a run-time check to figure out whether a supposedly non-nullable field has been initialized, that sounds like a code smell to me.
I see the fact that something can only be initialized late as an internal matter of a library/module/part of project/etc. that should never leak outside of it (i.e. the outside world should never have to deal with potentially uninitialized objects - if it has to, then the field is not really non-nullable).
Agreed. If you need to find out if a lateinit field has actually been initialized, and you aren't writing a library that is responsible for initializing it, that means your field is really nullable so just mark it as such.
If you are writing a library I think you should be able to use reflection to figure out if it has actually been initialized, or worst case scenario just catch the exception that is thrown if it isn't initialized.
If you are writing a library I think you should be able to use reflection to figure out if it has actually been initialized, or worst case scenario just catch the exception that is thrown if it isn't initialized.
Basically, complex initialization logic, when a field can be initialized in multiple different ways.
Once the initialization step is complete, the field will never be null and no checking will be required ever again. But in the current state of affairs, you just have to make it nullable and sprinkle the `!!` operator all over your code.
Once the initialization step is complete, the field will never be null and no checking will be required ever again. But in the current state of affairs, you just have to make it nullable and sprinkle the `!!` operator all over your code.
Use two properties. First is nullable and holds real value, so you can check it. Second is non-nullable and just a getter for first.
I haven't used Kotlin, so...
Why are nullable types a pain in the ass? If I recall correctly, isn't a nullable something you have to opt into? Basically you can't get null on accident, but you can opt into it when null makes sense?
Why are nullable types a pain in the ass? If I recall correctly, isn't a nullable something you have to opt into? Basically you can't get null on accident, but you can opt into it when null makes sense?
I don't agree that it's a pain, it's a relief, actually. Though Kotlin's solution is half-baked. Java library is full of Type!, so Kotlin doesn't really help there, even when there are clear contracts which types are nullable or not. They tried to implement external annotations, but for some reason ditched that idea, and I think, that it should be done. Of course there will be places with Type!, but for majority of cases, even static bytecode analysis should be able to annotate majority of types as either nullable or not. So even if Kotlin is better than plain Java in that regard (it checks for not-null at Java-Kotlin boundaries and detects NPE earlier), it still can be improved.
> Java-Kotlin boundaries ... For me so far this boils down to annotating Java API methods that return null with @Nullable. If Java API never returns null collections then this is not too bad in practice.
[deleted]
I've never used Kotlin (and I haven't touched Java in more than a decade) so I can't really comment on the usability issues of the language. That being said I think he completely misses the point of the nullable types and he's fighting them because he's treating them like "void * " instead of trying to understand how to work with them.
In particular, to his:
> Where does this absolutely crazy confidence of a compiler that each character of my program is an element of multithread concurrency come from?
I'd reply: why would the compiler have this absolutely crazy confidence that a particular character of your program isn't an element of a multithread concurrency? Why do you want your compiler to make assumptions about your code if it's not able to enforce them?
I don't know what's the right way to write what he means in Kotlin but in Rust it'd be something like:
That being said I can imagine that having to interface with Java (which doesn't share the same "mindset") might be frustrating. It helps when all the language and its libraries are designed from the ground up around a certain paradigm.
In particular, to his:
> Where does this absolutely crazy confidence of a compiler that each character of my program is an element of multithread concurrency come from?
I'd reply: why would the compiler have this absolutely crazy confidence that a particular character of your program isn't an element of a multithread concurrency? Why do you want your compiler to make assumptions about your code if it's not able to enforce them?
I don't know what's the right way to write what he means in Kotlin but in Rust it'd be something like:
match (value) {
Some(v) => v,
None => 0
}
Or alternatively: value.unwrap_or(0)
Which are both safe and, in my opinion, clearer than the proposed code in TFA. I would assume that Kotlin has a similar syntax for that use case?That being said I can imagine that having to interface with Java (which doesn't share the same "mindset") might be frustrating. It helps when all the language and its libraries are designed from the ground up around a certain paradigm.
I agree. I used to argue like the author, that null is a thing in computing and I just had to deal with it.
But after spending some time in Rust and Swift, I learnt a whole new way of thinking about it. The killer combination is:
no null + immutable
I've taken this to javascript, and it is so much better. Just don't accept null values anywhere in the model code, and suddenly writing view code is super easy.
No more endless if-cases or ternary to deal with nulls.
But after spending some time in Rust and Swift, I learnt a whole new way of thinking about it. The killer combination is:
no null + immutable
I've taken this to javascript, and it is so much better. Just don't accept null values anywhere in the model code, and suddenly writing view code is super easy.
No more endless if-cases or ternary to deal with nulls.
Yes. I usually refer to them as "optional" values, rather than "nullable" values. Something should only be null if it is truly optional to the class, view or whatever, which is a pretty rare thing. If a class requires a certain value to be able to do it's work, and that value is missing (null), why was the class even instantiated? That should be a big no-no.
In Kotlin you can do:
when (value) {
1 -> ...,
2 -> ...,
null -> ...
}
The compilers doesn't let you forget to check for null.
It's smart enough to correctly evaluate and cast nested expression.
when (value) {
is null -> "nothing to see"
is Number -> "a Number: ${value + 1}"
is String -> when {
it.isBlank() -> "Blank String"
null -> /* compiler error, you already checked for null */
else -> it.trim()
}
}
Or val x = value ?: 5 /* use 5 if value is null)
val x = value?.some?.nullable?.chain ?: 5 /* use 5 if the chain can't be called */
Or val x = value?.let { /* work on non-null value */ } ?: alternativeYou can do that exact same matching in Scala (except it's value match { case Some(...}), and the great way to avoid things changing are to use non-mutable types (a val instead of a var).
I have not played much with Kotlin, but I feel like Scala deals with a lot of the issues he brings up already (although it has a ton of issues itself; some stemming from it being so tied to the underlying Java Runtime and others from bad design decisions).
I have not played much with Kotlin, but I feel like Scala deals with a lot of the issues he brings up already (although it has a ton of issues itself; some stemming from it being so tied to the underlying Java Runtime and others from bad design decisions).
It's painful for the compiler to assume non-local variables may always be shared if the language does not reify the concept of "sharedness" like Rust does. Given that in any reasonable program the vast majority of state is non-shared, it really is somewhat unreasonable for the compiler to always assume the worst without the programmer even being able to override it.
Honestly I started out thinking this article was satirical, and I'm not at all convinced it's not now, but it's a bit difficult to tell around the author's probably-non-native grasp of English.
Everyone else seems to be treating it like it's serious though. Maybe I'm just too infused with the joy of Rust, Haskell and other such languages to think anything slightly hysterical-sounding about the utility of non-nullable types and the possibility of mutation by other threads can possibly be anything other than parody.
Everyone else seems to be treating it like it's serious though. Maybe I'm just too infused with the joy of Rust, Haskell and other such languages to think anything slightly hysterical-sounding about the utility of non-nullable types and the possibility of mutation by other threads can possibly be anything other than parody.
> This article is based on Your Language Sucks in the form of half a joke.
First I though the article was being ironic, I end up understanding by thinking it is a completely a joke, not half a joke. Otherwise I do not understand:
- why the author compares '?' with '?:' as they are completely different things
- complains about automatic casting of class variables without understanding that the languages have threads ( "thread intersections occur on a very small size of the source code, but due to compiler’s repressive care about this feature" -> it is much better to crash your program randomly in production than a compiler error, ouh yeah!)
- complains kotlin has a useless for that only saves some lines of code (like few lines of codes repeated for every for you write in the code, which it is a lot of code)
- complains about the alias without understanding generics
- complains about poor generics without understanding generics
- etc
100% I wont hire him as a engineer
First I though the article was being ironic, I end up understanding by thinking it is a completely a joke, not half a joke. Otherwise I do not understand:
- why the author compares '?' with '?:' as they are completely different things
- complains about automatic casting of class variables without understanding that the languages have threads ( "thread intersections occur on a very small size of the source code, but due to compiler’s repressive care about this feature" -> it is much better to crash your program randomly in production than a compiler error, ouh yeah!)
- complains kotlin has a useless for that only saves some lines of code (like few lines of codes repeated for every for you write in the code, which it is a lot of code)
- complains about the alias without understanding generics
- complains about poor generics without understanding generics
- etc
100% I wont hire him as a engineer
The part about maps is wrong, you can write them like
Also, regarding that final sentence about matrices, that sounds like a perfect use case for type aliases.
val myMap = mapOf(
"A" to 10,
"B" to 20
)
etc.Also, regarding that final sentence about matrices, that sounds like a perfect use case for type aliases.
What convinced me that Kotlin wasn't for me was Andrey Breslav's reasoning why he insisted on sticking to Scala's confusing "Unit" terminology instead of terminology that actually makes sense [0].
> Why we don't call it "Void": because the word "void" means "nothing", and there's another type, Nothing, that means just "no value at all", i.e. the computation did not complete normally (looped forever or threw an exception). We could not afford the clash of meanings.
And then he doubles down on it in the comments:
> "Unit" just stands for "something that has only one value", it's a traditional name, comes from functional languages. I agree that this name is not very intuitive, but we failed to invent a better name.
He managed to convince me that Kotlin's highest priority was pointless academic nitpicking, and that's not what I'm looking for in a language. What I wanted was a solid JVM language that avoids all the unnecessary boilerplate of Java without diving headfirst into a morass of functional programming like Scala. I want something that's 100% practicality and straightforwardness, not something that thinks trying to emulate higher math is something to aspire to. Well, I guess there's still Jython, even if it is stuck on an ancient version of Python... (edit: oh, and Groovy... forgot about it because most people just use it for Gradle scripting)
[0] https://stackoverflow.com/questions/22654932/what-is-the-pur...
> Why we don't call it "Void": because the word "void" means "nothing", and there's another type, Nothing, that means just "no value at all", i.e. the computation did not complete normally (looped forever or threw an exception). We could not afford the clash of meanings.
And then he doubles down on it in the comments:
> "Unit" just stands for "something that has only one value", it's a traditional name, comes from functional languages. I agree that this name is not very intuitive, but we failed to invent a better name.
He managed to convince me that Kotlin's highest priority was pointless academic nitpicking, and that's not what I'm looking for in a language. What I wanted was a solid JVM language that avoids all the unnecessary boilerplate of Java without diving headfirst into a morass of functional programming like Scala. I want something that's 100% practicality and straightforwardness, not something that thinks trying to emulate higher math is something to aspire to. Well, I guess there's still Jython, even if it is stuck on an ancient version of Python... (edit: oh, and Groovy... forgot about it because most people just use it for Gradle scripting)
[0] https://stackoverflow.com/questions/22654932/what-is-the-pur...
> Groovy... forgot about it because most people just use it for Gradle scripting
You can use Kotlin for Gradle scripting as well since Gradle 3.0 released about 6 months ago. Given that Gradleware also said they're switching their preferred language for Gradle addons to Kotlin from that version onwards, I'm guessing the same will happen for build scripts some day.
You can use Kotlin for Gradle scripting as well since Gradle 3.0 released about 6 months ago. Given that Gradleware also said they're switching their preferred language for Gradle addons to Kotlin from that version onwards, I'm guessing the same will happen for build scripts some day.
This seems like nitpicking as well. If the intention is that everything is an expression (that seems to be it, to me) then you have to have a Unit, no mater what you call it. I'd rather have a name that 3 academics know than override a distinct concept (Void) or make something entirely new.
I'm not sure how "tongue in cheek" this article was supposed to be, but most of his issues with the problem can be solved by buying into the language.
For:
Not sure what he's upset about, but there are many options in the standard library. I almost never use the language construct for it, and I'm very happy with it.
Nullable:
I never expected nullable to completely save me from all issues. It has the same function as the @Nullable and @NotNull annotations (and all the other variants). It gives you a simpler way to reason about nullability, and forces you to respond to it; you can't just ignore it.
Smart cast on mutable
I get that this sucks, but they can't show it to happen. Yes, the chances are small, but there could be concurrency issues. They have to account for that, and can't tell if your code is multi threaded or not.
Most of the time his issue can be solved with scoping, using ?.let or ?.apply
Java Nullability
What do you want from them? They have no ability whatsoever to control this. Either get annotations in the Java code if you can modify it (which is a good idea anyways), or wrap it and provide nullability information.
Assignment is not an expression
He got it. It's too protect against if(v=v). I know he makes a joke about automatic type casting, but if you're not assigning the result to anything, the logic in the if will still fail.
Elvis operator
I assume they removed Javas ternary operator to not get confused with the Elvis operator. ¯\_(ツ)_/¯ I kind of agree with him here unless I'm missing something
Automatic type casting
Frustrating, but safe
Type alias
I'm fine with it for simple cases, didn't want to parse his problem.
Generics
I hear his gripe, but at the end of the day Kotlin generics are Java generics. If there are some potential bugs in the compiler, report them and move on.
Structure syntax
I assume he means list and map literals. I agree it would be nice. Not sure why they didn't include it.
For:
Not sure what he's upset about, but there are many options in the standard library. I almost never use the language construct for it, and I'm very happy with it.
Nullable:
I never expected nullable to completely save me from all issues. It has the same function as the @Nullable and @NotNull annotations (and all the other variants). It gives you a simpler way to reason about nullability, and forces you to respond to it; you can't just ignore it.
Smart cast on mutable
I get that this sucks, but they can't show it to happen. Yes, the chances are small, but there could be concurrency issues. They have to account for that, and can't tell if your code is multi threaded or not.
Most of the time his issue can be solved with scoping, using ?.let or ?.apply
Java Nullability
What do you want from them? They have no ability whatsoever to control this. Either get annotations in the Java code if you can modify it (which is a good idea anyways), or wrap it and provide nullability information.
Assignment is not an expression
He got it. It's too protect against if(v=v). I know he makes a joke about automatic type casting, but if you're not assigning the result to anything, the logic in the if will still fail.
Elvis operator
I assume they removed Javas ternary operator to not get confused with the Elvis operator. ¯\_(ツ)_/¯ I kind of agree with him here unless I'm missing something
Automatic type casting
Frustrating, but safe
Type alias
I'm fine with it for simple cases, didn't want to parse his problem.
Generics
I hear his gripe, but at the end of the day Kotlin generics are Java generics. If there are some potential bugs in the compiler, report them and move on.
Structure syntax
I assume he means list and map literals. I agree it would be nice. Not sure why they didn't include it.
> What do you want from them? They have no ability whatsoever to control this. Either get annotations in the Java code if you can modify it (which is a good idea anyways), or wrap it and provide nullability information.
What I want is consistency. In Scala optional is always safe and null is never safe, and that's true whether you're calling a Scala method or a Java method (even the most perverse Java libraries don't declare themselves as returning Optional<String> and then return null). In Kotlin null is safe some of the time and not safe other times, e.g. if you're passing it to a method it might be fine or might not.
What I want is consistency. In Scala optional is always safe and null is never safe, and that's true whether you're calling a Scala method or a Java method (even the most perverse Java libraries don't declare themselves as returning Optional<String> and then return null). In Kotlin null is safe some of the time and not safe other times, e.g. if you're passing it to a method it might be fine or might not.
In Kotlin null is always safe. The problem is that types imported from Java are "platform types", i.e. Kotlin doesn't know whether they're nullable or non-nullable, and it's up to the programmer to check their API docs and insert the appropriate handling at the call site.
For example, in my own pure-Kotlin code, "val id: String" is a non-nullable type "String"; the compiler will guarantee that it is never null. "val url: URL?" is a nullable type "URL?"; the compiler checks for null, and I need to use ? or !! or a smart-cast to access it. HttpServletRequest.pathInfo is a platform type "String!", is marked as such in autocomplete, and could be either null or non-null based on the API; it's up to me to use it appropriately.
The difference between platform types and nullable types is that platform types can be silently coerced to non-null types, even though they may be null. This was an ergonomic decision; you use so many Java APIs in Kotlin that the language would be nearly unusable afterwards (kinda like how Scala, rather than being a "better Java", has developed its own ecosystem of idiomatic Scala code). It's a potential safety hole, but it's clearly documented; think of it like getting memory management right in a C FFI.
For example, in my own pure-Kotlin code, "val id: String" is a non-nullable type "String"; the compiler will guarantee that it is never null. "val url: URL?" is a nullable type "URL?"; the compiler checks for null, and I need to use ? or !! or a smart-cast to access it. HttpServletRequest.pathInfo is a platform type "String!", is marked as such in autocomplete, and could be either null or non-null based on the API; it's up to me to use it appropriately.
The difference between platform types and nullable types is that platform types can be silently coerced to non-null types, even though they may be null. This was an ergonomic decision; you use so many Java APIs in Kotlin that the language would be nearly unusable afterwards (kinda like how Scala, rather than being a "better Java", has developed its own ecosystem of idiomatic Scala code). It's a potential safety hole, but it's clearly documented; think of it like getting memory management right in a C FFI.
> In Kotlin null is always safe. The problem is that types imported from Java...
Well yeah. But Kotlin advocates say you're supposed to use those extensively.
> think of it like getting memory management right in a C FFI.
Yeah, that's pretty terrifying. I would never want to use a language that expected me to make extensive use of a C FFI. It was bad enough when I used Python, and that involved a lot less C than Kotlin does Java.
Well yeah. But Kotlin advocates say you're supposed to use those extensively.
> think of it like getting memory management right in a C FFI.
Yeah, that's pretty terrifying. I would never want to use a language that expected me to make extensive use of a C FFI. It was bad enough when I used Python, and that involved a lot less C than Kotlin does Java.
You would normally check null on language boundaries:
> Yeah, that's pretty terrifying. I would never want to use a language that expected me to make extensive use of a C FFI. It was bad enough when I used Python, and that involved a lot less C than Kotlin does Java.
You do this every day, regardless of which language you use. Your POSIX APIs and OS services are all in C. That they generally work is because numerous people have invested the time into putting appropriate error checks & memory safety into the host language bindings.
It's the same story with Kotlin: when you write code that uses a language (Java) that doesn't have appropriate safety mechanisms, you read the docs & code carefully, and then you use the safety mechanisms that your language (Kotlin) gives you for all the code you write. There is really nothing Kotlin can do about this: the original code is written in a language that doesn't make the distinction between nullable and non-nullable, so there is no information in the type system about whether it should map to a nullable or non-nullable type.
val path: String = req.pathInfo ?: ""
Now path is a non-nullable String, and you can be sure that it will never contain null, and you have specified exactly what the behavior is if the Java API returned it.> Yeah, that's pretty terrifying. I would never want to use a language that expected me to make extensive use of a C FFI. It was bad enough when I used Python, and that involved a lot less C than Kotlin does Java.
You do this every day, regardless of which language you use. Your POSIX APIs and OS services are all in C. That they generally work is because numerous people have invested the time into putting appropriate error checks & memory safety into the host language bindings.
It's the same story with Kotlin: when you write code that uses a language (Java) that doesn't have appropriate safety mechanisms, you read the docs & code carefully, and then you use the safety mechanisms that your language (Kotlin) gives you for all the code you write. There is really nothing Kotlin can do about this: the original code is written in a language that doesn't make the distinction between nullable and non-nullable, so there is no information in the type system about whether it should map to a nullable or non-nullable type.
> You do this every day, regardless of which language you use. Your POSIX APIs and OS services are all in C. That they generally work is because numerous people have invested the time into putting appropriate error checks & memory safety into the host language bindings.
Not when I'm working on ocaml unikernels, but sure. The point is I only use languages where those bindings already exist and are maintained; I wouldn't want to use a language that expected me to use a C FFI directly in day-to-day work. Whereas the Kotlin narrative is that you do use the Java "FFI" directly in day-to-day work.
Not when I'm working on ocaml unikernels, but sure. The point is I only use languages where those bindings already exist and are maintained; I wouldn't want to use a language that expected me to use a C FFI directly in day-to-day work. Whereas the Kotlin narrative is that you do use the Java "FFI" directly in day-to-day work.
I don't see a huge difference between Scala's Options and Kotlin's null safety. In Scala, you can still do this:
val x: Option[Int] = nullIn theory you can. But you'd know to immediately fail that line in code review (or better still, enforce it at build time via wartremover). Whereas in Kotlin
someMethod(null)
might be perfectly legitimate or might not, but you can't tell without knowing the details of someMethod (whether it's a Java method or not).Maybe I'm missing something but how is that different from Scala or Java?
That will fail at compile time in Kotlin if the method is written in Kotlin and has a non-nullable parameter. Even if it's Java I think it will fail at compile time if the Java parameter is annotated with @NotNull.
If it is a Java method and not annotated with @NotNull then of course it will be allowed at compile time because how could it possibly not be allowed? What are you saying is preferable in that case?
That will fail at compile time in Kotlin if the method is written in Kotlin and has a non-nullable parameter. Even if it's Java I think it will fail at compile time if the Java parameter is annotated with @NotNull.
If it is a Java method and not annotated with @NotNull then of course it will be allowed at compile time because how could it possibly not be allowed? What are you saying is preferable in that case?
The trouble is in idiomatic Kotlin you have lines like that, because when you want to represent absence you use nullable parameters. So you can't assume a line like that is wrong. But you can't assume it's correct just because it compiled either, because as you say it might be an unannotated Java method.
In idiomatic Scala you would never have a line like that (because you represent absence with optionals rather than null). So you can insta-fail any line like that in code review (or give very careful review in the rare case that it's calling a Java method that accepts a nullable parameter, but good modern Java libraries are moving away from those, so that case should go away).
In idiomatic Scala you would never have a line like that (because you represent absence with optionals rather than null). So you can insta-fail any line like that in code review (or give very careful review in the rare case that it's calling a Java method that accepts a nullable parameter, but good modern Java libraries are moving away from those, so that case should go away).
The main difference is that Kotlin can do both: nullable types or `Option` (and you'd never use `Option` unless you are dealing with it coming from Java, obviously).
Scala can only do `Option` since it doesn't have built in support for nullable types.
I fail to see how your point shows any superiority from Scala here, it's the other way around.
At the end of the day, both languages have nullability support but since it's optional in Scala (because library based), Kotlin's approach is superior in that it requires developers to deal with nullability, while Scala developers can ignore it any time they want.
Scala can only do `Option` since it doesn't have built in support for nullable types.
I fail to see how your point shows any superiority from Scala here, it's the other way around.
At the end of the day, both languages have nullability support but since it's optional in Scala (because library based), Kotlin's approach is superior in that it requires developers to deal with nullability, while Scala developers can ignore it any time they want.
Nullable types are worse than Option in every way that matters (yes they save a few bytes of memory occasionally, but who the hell cares, if you care about shaving 8 bytes off an object you won't be using the JVM in the first place. It's certainly not worth the cost of working with a weird, second-class type that you can't abstract over like other types). Why do you want an extra way of representing the same thing with more special cases?
> Kotlin's approach is superior in that it requires developers to deal with nullability
But it doesn't force them to deal with nullability when interacting with Java which is the only case where it matters.
Scala calling Scala: you never use null, you never hit a problem with null.
Scala calling Java: you have to manually check return values for older libraries but at least passing in null can't happen.
Kotlin calling Kotlin: you use null a lot, you never hit a problem with null.
Kotlin calling Java: you have to manually check return values for older libraries and won't notice where you're passing in null because that's a normal, safe thing to do when calling Kotlin.
> Kotlin's approach is superior in that it requires developers to deal with nullability
But it doesn't force them to deal with nullability when interacting with Java which is the only case where it matters.
Scala calling Scala: you never use null, you never hit a problem with null.
Scala calling Java: you have to manually check return values for older libraries but at least passing in null can't happen.
Kotlin calling Kotlin: you use null a lot, you never hit a problem with null.
Kotlin calling Java: you have to manually check return values for older libraries and won't notice where you're passing in null because that's a normal, safe thing to do when calling Kotlin.
I think there's some misunderstanding about what goes on with an Option<T> type in the compiler. In a sane implementation of Option<T>, there's no space premium for using it over T. The compiler compiles Some<T> to a pointer to T, and it compiles None to null. Swift, Rust, and Haskell all do this; I don't know Scala that well, but I'd imagine it would too unless there's some edge case in Scala<->Java interop.
Kotlin's nullable types and Option are exactly the same thing, except with some implicit conversions and smart casts available (marked by the IDE). Using Kotlin & Rust syntax, I make the following mental equivalencies:
The two ways that Kotlin nullable types differ from Options in other languages is that you have smart casts (if the compiler can prove that the value is never null, you can use it as a normal variable without explicitly unwrapping), and you have automatic coercions from platform types.
Also, I almost never use null in idiomatic Kotlin programming.
Kotlin's nullable types and Option are exactly the same thing, except with some implicit conversions and smart casts available (marked by the IDE). Using Kotlin & Rust syntax, I make the following mental equivalencies:
T? <==> Option<T>
maybeNull!! <==> maybeNull.unwrap()
maybeNull ?: default <==> maybeNull.unwrap_or(default)
maybeNull == null <==> maybeNull.isNone()
maybeNull != null <==> maybeNull.isSome()
maybeNull?.method() <==> maybeNull.map(|v| v.method())
maybeNull?.method() ?: default <==> maybeNull.map_or(|v| v.method(), default)
The comparison is even more apparent if you're used to Swift, which has a separate Optional type but the same syntactic sugar as Kotlin for manipulating it.The two ways that Kotlin nullable types differ from Options in other languages is that you have smart casts (if the compiler can prove that the value is never null, you can use it as a normal variable without explicitly unwrapping), and you have automatic coercions from platform types.
Also, I almost never use null in idiomatic Kotlin programming.
> In a sane implementation of Option<T>, there's no space premium for using it over T. The compiler compiles Some<T> to a pointer to T, and it compiles None to null. Swift, Rust, and Haskell all do this; I don't know Scala that well, but I'd imagine it would too unless there's some edge case in Scala<->Java interop.
This is wrong for Scala, and I think it's wrong for the other cases.
> Kotlin's nullable types and Option are exactly the same thing
Except that it doesn't nest properly. Which means it's not compositional: if you write code that works with T?, it will have surprising behaviour if someone ever passes a nullable type for T. And you can't abstract over it, though that's a more general problem of Kotlin not having HKT.
> Also, I almost never use null in idiomatic Kotlin programming.
How do you represent optionality then? I use Option and None pretty often in idiomatic Scala programming (and when I don't it's usually because I'm using a richer variant on the same thing e.g. Either or a custom sum type appropriate to the business requirements) - there's a reason it's such a common example, it comes up in realistic problems all the time.
This is wrong for Scala, and I think it's wrong for the other cases.
> Kotlin's nullable types and Option are exactly the same thing
Except that it doesn't nest properly. Which means it's not compositional: if you write code that works with T?, it will have surprising behaviour if someone ever passes a nullable type for T. And you can't abstract over it, though that's a more general problem of Kotlin not having HKT.
> Also, I almost never use null in idiomatic Kotlin programming.
How do you represent optionality then? I use Option and None pretty often in idiomatic Scala programming (and when I don't it's usually because I'm using a richer variant on the same thing e.g. Either or a custom sum type appropriate to the business requirements) - there's a reason it's such a common example, it comes up in realistic problems all the time.
I think this comment shows a very fundamental lack of understanding how different languages handle errors.
In Java (and Kotlin) -- ignoring the topic of exceptions as an alternative -- errors like "missing value" are communicated with null.
So in Java you first have an error handling problem, you deal with it by returning null ... now you have a null handling problem!
Kotlin tries to put some band-aid around nulls by making them more typed than in Java.
In Scala, errors are handled with bog-standard library types like Option, Either, Try, Validation, not some special language built-in. These types are not facilities to handle null (shown by the fact that all these types happily accept null as a valid value), they are facilities to handle errors.
They handle errors better than Java or Kotlin, because these types allow developers to choose the appropriate type for a specific error case and retain the structure of a computation. To expand on the second point: Nullable types cannot be nested, Scala's types can and regularly are.
This allows Scala to compose operations while carrying errors up to the point where they can be handled easily.
If you have an operation returning an Option[T], and want to run an option on the value returning an Either[S, T] then simply looking at the resulting value will tell you if things succeeded or where exactly things went wrong.
TL;DR: Kotlin tries to put some band-aid around Java's broken approach of handling errors with null, Scala deals with errors correctly in the first place.
In Java (and Kotlin) -- ignoring the topic of exceptions as an alternative -- errors like "missing value" are communicated with null.
So in Java you first have an error handling problem, you deal with it by returning null ... now you have a null handling problem!
Kotlin tries to put some band-aid around nulls by making them more typed than in Java.
In Scala, errors are handled with bog-standard library types like Option, Either, Try, Validation, not some special language built-in. These types are not facilities to handle null (shown by the fact that all these types happily accept null as a valid value), they are facilities to handle errors.
They handle errors better than Java or Kotlin, because these types allow developers to choose the appropriate type for a specific error case and retain the structure of a computation. To expand on the second point: Nullable types cannot be nested, Scala's types can and regularly are.
This allows Scala to compose operations while carrying errors up to the point where they can be handled easily.
If you have an operation returning an Option[T], and want to run an option on the value returning an Either[S, T] then simply looking at the resulting value will tell you if things succeeded or where exactly things went wrong.
None --> First operation did not result in a value
Some(Left(fail)) --> Second operation failed with cause "fail"
Some(Right(value)) --> All operations succeeded with result "value"
In Java and Kotlin all you would get would be a bare null, with a probability of people giving up on (typed) null completely and just throwing an (unchecked) exception instead.TL;DR: Kotlin tries to put some band-aid around Java's broken approach of handling errors with null, Scala deals with errors correctly in the first place.
Outside of very specific low-level operations like Maps or taking .first() of an empty list, you don't handle errors with null in either Java or Kotlin. You handle them with exceptions. You can't ignore the topic of exceptions as an alternative - that is the idiomatic way to signal an error in Java and Kotlin. Nobody seriously considers having high-level APIs return "null" on error; for one, there's no way to differentiate the different things that can go wrong.
Now, there's an alternative school of thought that's gaining currency in languages like Rust, Go, and apparently Scala, and is how we used to handle them in C. That's to return special out-of-band values as part of the result type. The Either type in these languages (realistically, you wouldn't use an Option for any high-level API) in these languages makes this really easy, and sum types in general make pattern-matching on the specific error code & passing back diagnostic information a lot easier than it was in C.
I don't really want to get into a debate about error-by-value vs. error-by-exception, because there are good arguments on both sides of the debate and it's far from a settled question. Personally, if I were starting a new language today, I'd use return values for "expected" error conditions and some sort of panic/recover mechanism to indicate that the programmer forgot to handle a case.
But there's a really strong reason why Kotlin uses exceptions: interoperability with Java code. It's the same reason that Google bans exceptions in their C++ code: you can't really change the error handling mechanism of a large body of existing code, because the existing code's API all assumes certain language conventions, and if you break those conventions, you might as well rebuild the ecosystem from scratch.
So sure, if I were starting a language & ecosystem from the ground up, I'd probably use return values & pattern-matching. But I use Kotlin specifically because I want a "better Java", and need to integrate with Java libraries that may return null and will throw exceptions. If I didn't have those constraints, I'd use Rust instead.
Now, there's an alternative school of thought that's gaining currency in languages like Rust, Go, and apparently Scala, and is how we used to handle them in C. That's to return special out-of-band values as part of the result type. The Either type in these languages (realistically, you wouldn't use an Option for any high-level API) in these languages makes this really easy, and sum types in general make pattern-matching on the specific error code & passing back diagnostic information a lot easier than it was in C.
I don't really want to get into a debate about error-by-value vs. error-by-exception, because there are good arguments on both sides of the debate and it's far from a settled question. Personally, if I were starting a new language today, I'd use return values for "expected" error conditions and some sort of panic/recover mechanism to indicate that the programmer forgot to handle a case.
But there's a really strong reason why Kotlin uses exceptions: interoperability with Java code. It's the same reason that Google bans exceptions in their C++ code: you can't really change the error handling mechanism of a large body of existing code, because the existing code's API all assumes certain language conventions, and if you break those conventions, you might as well rebuild the ecosystem from scratch.
So sure, if I were starting a language & ecosystem from the ground up, I'd probably use return values & pattern-matching. But I use Kotlin specifically because I want a "better Java", and need to integrate with Java libraries that may return null and will throw exceptions. If I didn't have those constraints, I'd use Rust instead.
I have seen plenty of libraries using null to indicate an error. If this wasn't the case, as you allege, then why do Kotlin devs act like it's such a big deal? Why add a language feature for something that isn't even considered an issue instead of e.g. improving how Kotlin handles checked exceptions?
Go uses multiple returns for this, IIRC, not an Either type.
> Kotlin's nullable types and Option are exactly the same thing
I don't think that's accurate.
First of all, Scala's Option is a monad, no such thing with Kotlin's nullable type.
And second, Kotlin's nullability is enforced by the compiler so you can't escape it. In Scala, you are welcome to ignore nullable values whenever it pleases you (or when you forget to wrap them in an Option).
They solve a similar problem but with different approaches and different pros and cons.
I don't think that's accurate.
First of all, Scala's Option is a monad, no such thing with Kotlin's nullable type.
And second, Kotlin's nullability is enforced by the compiler so you can't escape it. In Scala, you are welcome to ignore nullable values whenever it pleases you (or when you forget to wrap them in an Option).
They solve a similar problem but with different approaches and different pros and cons.
> First of all, Scala's Option is a monad, no such thing with Kotlin's nullable type.
Is your point that Kotlin's type violates the monad laws? It does, but all that means is that certain constructs behave in really surprising ways and refactors that look obviously safe aren't. I don't see how that could ever be construed as an advantage.
If Kotlin's type conformed to the laws (which would be a great improvement) then it would be a monad even if you ignored that fact. All ignoring it means is that you don't get to take advantage of some common generic functions, but have to write them out separately every time instead.
> And second, Kotlin's nullability is enforced by the compiler so you can't escape it. In Scala, you are welcome to ignore nullable values whenever it pleases you (or when you forget to wrap them in an Option).
In Scala you're forced to check Options (or, better, handle both cases), so it's the same. Yes, if a "bare" null were somehow to get into your program then you would miss it. But where would that null come from? Only from a Java library and that's exactly the same problem in Kotlin. (Theoretically you could also get one coming out of some really bad Scala code, but the library ecosystem knows better than that, and in your own code you either simply don't do that or you use wartremover to enforce that you never do that)
Is your point that Kotlin's type violates the monad laws? It does, but all that means is that certain constructs behave in really surprising ways and refactors that look obviously safe aren't. I don't see how that could ever be construed as an advantage.
If Kotlin's type conformed to the laws (which would be a great improvement) then it would be a monad even if you ignored that fact. All ignoring it means is that you don't get to take advantage of some common generic functions, but have to write them out separately every time instead.
> And second, Kotlin's nullability is enforced by the compiler so you can't escape it. In Scala, you are welcome to ignore nullable values whenever it pleases you (or when you forget to wrap them in an Option).
In Scala you're forced to check Options (or, better, handle both cases), so it's the same. Yes, if a "bare" null were somehow to get into your program then you would miss it. But where would that null come from? Only from a Java library and that's exactly the same problem in Kotlin. (Theoretically you could also get one coming out of some really bad Scala code, but the library ecosystem knows better than that, and in your own code you either simply don't do that or you use wartremover to enforce that you never do that)
> In Scala you're forced to check Options
But you're never forced to use them. That's the main problem with Scala's approach: it's entirely er... optional.
The Scala compiler will never tell you "You can't write a.foo() because a may be null".
> Only from a Java library and that's exactly the same problem in Kotlin
No, it's not. The Kotlin compiler will not let such values enter Kotlin code until you've told it precisely whether that type is nullable or not.
Scala has no such mechanism so null values can enter Scala code without the compiler having any say over it.
But you're never forced to use them. That's the main problem with Scala's approach: it's entirely er... optional.
The Scala compiler will never tell you "You can't write a.foo() because a may be null".
> Only from a Java library and that's exactly the same problem in Kotlin
No, it's not. The Kotlin compiler will not let such values enter Kotlin code until you've told it precisely whether that type is nullable or not.
Scala has no such mechanism so null values can enter Scala code without the compiler having any say over it.
Some(T) is not a pointer to a T in Rust. It's just a T.
If it were a pointer type, then yeah, it would be a pointer.
If it were a pointer type, then yeah, it would be a pointer.
Presumably it has a different representation from a bare T? E.g. if T=Boolean then there are only two possible values for it, whereas there are three possible values for Option[T], so they need different kinds of storage (obv. in practice a boolean probably doesn't use a whole byte so you can do a kind of packed optimization, but you can't scale that arbitrarily far for deeply nested Option[Option[...Option[T]...], and this is an issue one would expect to hit with an integer or pointer where the natural representation is a full word that doesn't have any "spare" states).
Yes, there's an optimization. Specifically, there's a trait called "NonZero"; if T implements it, then Option<T> uses all zeros to represent None, and so std::mem::size_of::<Option<T>>() == std::mem::size_of::<T>(); If not, then you have to store a tag.
I don't know why you keep coming back to the Java interop aspect:
- It's trivial to solve and really not a problem in practice. I'm pretty sure that when you think about and write code, it's 99% in Scala and very little about Java. "Oh, this value is coming from Java and therefore can be null, I'll wrap it in an Option. Done".
- Kotlin and Scala are exactly on the same level when it comes to interoperating with Java. And I bet we solve it the same way: catch the nullable values from Java as soon as possible before they enter the Scala/Kotlin world, and now we're safe again.
The more interesting case for me is pure Scala or pure Kotlin. And I very much prefer Kotlin's approach to nullability for all the reasons I've enumerated, but let's not go over this again.
- It's trivial to solve and really not a problem in practice. I'm pretty sure that when you think about and write code, it's 99% in Scala and very little about Java. "Oh, this value is coming from Java and therefore can be null, I'll wrap it in an Option. Done".
- Kotlin and Scala are exactly on the same level when it comes to interoperating with Java. And I bet we solve it the same way: catch the nullable values from Java as soon as possible before they enter the Scala/Kotlin world, and now we're safe again.
The more interesting case for me is pure Scala or pure Kotlin. And I very much prefer Kotlin's approach to nullability for all the reasons I've enumerated, but let's not go over this again.
Isn't pure Kotlin a much more unlikely scenario than pure Scala?
Kotlin devs certainly take pride at every opportunity of how much Kotlin piggybacks on Java, cf. collections.
I think you can't have it both ways, having a worse abstraction for handling errors while claiming that it works better with Java code and then claiming that "pure Kotlin code" is where the more interesting case is.
I think you can't have it both ways, having a worse abstraction for handling errors while claiming that it works better with Java code and then claiming that "pure Kotlin code" is where the more interesting case is.
No, JetBrains is actually working on Kotlin Native [1].
[1] https://kotlinlang.slack.com/messages/kotlin-native/
[1] https://kotlinlang.slack.com/messages/kotlin-native/
Yes, exactly, which makes the claim even more baffling.
Sure, they can just reimplement all their Java dependencies, but if they have to implement e.g. collections anyway, they could just have designed a better API in the first place, instead of being stuck with Java collections on platforms that don't even run Java.
Sure, they can just reimplement all their Java dependencies, but if they have to implement e.g. collections anyway, they could just have designed a better API in the first place, instead of being stuck with Java collections on platforms that don't even run Java.
> - It's trivial to solve and really not a problem in practice. I'm pretty sure that when you think about and write code, it's 99% in Scala and very little about Java. "Oh, this value is coming from Java and therefore can be null, I'll wrap it in an Option. Done".
That works for values coming out of Java but not for values going into Java.
That works for values coming out of Java but not for values going into Java.
Why on earth would you ever want to do this? This is not how you use an Option.
That's not the point. The point is that any safety you think you have by using an Option is a little bit hand-wavy.
That's not _bad_, but the argument being made by the OP was that nullability in Kotlin was unsafe and inconsistent.
That's not _bad_, but the argument being made by the OP was that nullability in Kotlin was unsafe and inconsistent.
My argument is that while you use null assignment and nullability in Kotlin, in Scala you don't work with nulls.
If your entire code base is in Kotlin, then nullability works exactly like Options. The difference is sugar. And the Kotlin compiler can handle null checks as well as the Scala compiler handles Options.
The problems arise when you interop with Java. At that point, shit can be null and you just don't know. My point is that it's the same problem in Scala as it is in Kotlin: Java interop adds a measure of unpredictability that needs to be handled at the touch points.
The problems arise when you interop with Java. At that point, shit can be null and you just don't know. My point is that it's the same problem in Scala as it is in Kotlin: Java interop adds a measure of unpredictability that needs to be handled at the touch points.
I agree on all these points.
Most of the problems with INTERCAL can be solved by "buying into the language".
I really don't understand the complaint about `for` loops... is it just that you can implement the same functionality with a custom-made function? I don't really get what's wrong with the language having some syntactic sugar baked in.
EDIT: should mention I don't know Kotlin so maybe there's something obvious I'm missing...
EDIT: should mention I don't know Kotlin so maybe there's something obvious I'm missing...
I didn't understand his point either (and I know Kotlin pretty well), but since 1.1 for-loops are basically deprecated in user code and you'd use the forEach method on Iterables:
collection.forEach { variable ->
....
}Could you elaborate, why they are deprecated, is there any sources? Actually I'm always wondering which form of "for" I should use and I always using language "for" because, well, it's part of language. I actually think that "for" will be enhanced in the future, e.g. including guards, nested loops, maps, etc, like Scala's version (or Haskell).
In general, people complaining about languages really should be showing the results of their examples. Since a lot of programmers are interested in languages, requiring knowledge of a language in such a blog post is hamstringing yourself by restricting your readership. Tell us what the result would be, precisely what line compiles and what throws an error.
The impedance mismatch with its intended target (mainly Java) is enough to make Kotlin a complete waste of time.
This is a reasonable critique of other JVM-hosted languages I've worked with (Clojure and JRuby), but the external interface of classes and methods in Kotlin are completely compatible with Java by design. And in practice, it's very easy to write mixed Java/Kotlin projects where you don't necessarily know (or care) whether the class you just imported was written in one or the other -- it just works how you'd expect. I'd be interested in specific examples where you found any impedance mismatch at all.
Maybe the closer similarity leads to more confusion and mix up.
Clojure works pretty well with Java, but is so different, you'll rarely get things mixed up between the two.
Just a guess, since I'm not the original commenter.
Clojure works pretty well with Java, but is so different, you'll rarely get things mixed up between the two.
Just a guess, since I'm not the original commenter.
On Android is the only sane path out of the Android Java fork, stale in Java 6, with cherry picked features from Java 7 and 8.
How so? In my experience Kotlin works nicely with Java.
I found it easier to just write Java.
This. But competition is good. And here's to Java 9 and eventually 10 and ...
I'm just getting started with Kotlin so can't comment too deeply on its problems but here are a couple things that have already annoyed me.
Kotlin is supposed to be less verbose but variable declarations and function signatures can get annoyingly long.
Where are the ints?
Kotlin is supposed to be less verbose but variable declarations and function signatures can get annoyingly long.
//java
String x;
//kotlin
var x : String?
Also, the whole fad of all new languages putting the type after the name doesn't make sense to me.Where are the ints?
Object objectThing;
Object someOtherObjectThing;
int thing;
int someOtherThing;
String stringThing;
String someOtherStringThing;
Much harder to find the ints in a long list of Kotlin fields: var objectThing:Any?
var someOtherObjectThing:Any?
var thing:Int?
var someOtherThing:Int?
var stringThing:String?
var someOtherStringThing:String?I dunno about you, but I usually go looking for variables (names) and rarely go looking for types. I rarely ask: "where are the ints?". I generally ask: "where are the invoices/urls/files/accounts/... ?".
I came across that annoyance in Android code where there were 30 some View fields of different types, TextView, ImageView etc. It wasn't my code so I didn't know what names had been used but I knew that I wanted to find one of the TextViews.
In Java Android code we normally group the fields by type to make them easier to find. But maybe we need a different convention for Kotlin.
In Java Android code we normally group the fields by type to make them easier to find. But maybe we need a different convention for Kotlin.
For Android UI components specifically I usually just include the type in the name. The types can get a little long but when you need to have and operate on
userNameTextView
userNameTextInputLayout
you end up saving time when looking back
Putting types after the variable is a fad that dates from the 60s - Pascal, and ML after it.
Usually the name of something is more important than the type. In dynamically typed languages, the name is all you get, and it's enough for a lot of programmers to be perfectly productive.
Usually the name of something is more important than the type. In dynamically typed languages, the name is all you get, and it's enough for a lot of programmers to be perfectly productive.
Normally you'd initialize the variable at the same time you declare it:
// Java
String x = new String()
// Kotlin
val x = String()In the question about ints, use any alignment editor plugin to align your colons.
I'm not even convinced you should be doing that.
I disagree with the idea that the Ints are hard to find in their post. That's just not the hard part of reading code, though it's a good example of a pointless micro-comment-benchmark.
I disagree with the idea that the Ints are hard to find in their post. That's just not the hard part of reading code, though it's a good example of a pointless micro-comment-benchmark.
Yes, they're easy to find here. They're even easier to find if the IDE supports coloring types. And even then, / or ctrl+f lets you quickly find what you know you're looking for.
[deleted]
from what I remember from the Scala Coursera course, the types after simplify a lot of the parsing, and also allow for type inference. Types first makes this almost impossible.
I probably misremembered quite a bit, but...
I probably misremembered quite a bit, but...
OK, this seems like a major fail on behalf of the Kotlin developer team:
Sure something in a JVM could be changed by another thread. But that would really be a bug no matter how you slice it. Doing type analysis like this seems like a minimal requirement for a modern language.
[1] http://imgur.com/nxdN9vc -- you can see that TypeScript can identify the remaining type. It works for optional null as well when strictNullChecks is enabled.
var value : Int? = null
fun F() : Int {
if ( value != null ) return 0
return value // error
}
Tracing value types through a function is a solved problem. Look at TypeScript: Almost the exact function above will work flawlessly. [1]Sure something in a JVM could be changed by another thread. But that would really be a bug no matter how you slice it. Doing type analysis like this seems like a minimal requirement for a modern language.
[1] http://imgur.com/nxdN9vc -- you can see that TypeScript can identify the remaining type. It works for optional null as well when strictNullChecks is enabled.
Look at the code carefully. The only way you can reach "return value" is if `value == null`, so the compiler complains with "Required Int, found Int?", because it (quite rightly!) can't prove that value is definitely not null
Let's change it to
"Smartcast to Int is impossible because value is a mutable property that could have been changed by this time". Right you are.
Let's change it to
var value : Int? = null
fun F() : Int {
if ( value == null ) return 0
return value // error
}
Now the compiler still complains, with a different error:"Smartcast to Int is impossible because value is a mutable property that could have been changed by this time". Right you are.
val value : Int? = null
fun F() : Int {
if ( value == null ) return 0
return value
}
This version compiles fine because `value` is a constant (though I think the compiler should've complained about dead code. You can never reach `return value`.) fun F(value : Int? ) : Int {
if ( value == null ) return 0
return value
}
This version also works fine.OK, you're right that he wrote the example incorrectly (mine was a copy-and-paste from his). The point still stands though.
> "Smartcast to Int is impossible because value is a mutable property that could have been changed by this time". Right you are.
I disagree. It cannot have been changed by that time, not unless it was changed in another thread, and it would be really, really nice for a language to have built-in threading intelligence to prevent that from being necessary to check. That's what I'm saying. TypeScript is single-threaded, so it can be certain that another thread hasn't changed it. Go is multithreaded, but except for genuine globals (which need to be protected by a mutex, but shouldn't be used for just about anything) you're piping information from one state to another, which again doesn't have a synchronization problem.
If you have to manually mark a variable as "safe" every time you use it, you're just going to need to do that constantly for lots of variables. And you still are stuck getting the thread safety right: The above code isn't safe if something from another thread can change value. Which I guess is the point of the exclamation points, but ... somehow I prefer the Rust approach of "Mark this block of code as unsafe." Not that I've used either language.
The fact that it works correctly as a parameter, though -- that does hit the 80% case, at least.
> "Smartcast to Int is impossible because value is a mutable property that could have been changed by this time". Right you are.
I disagree. It cannot have been changed by that time, not unless it was changed in another thread, and it would be really, really nice for a language to have built-in threading intelligence to prevent that from being necessary to check. That's what I'm saying. TypeScript is single-threaded, so it can be certain that another thread hasn't changed it. Go is multithreaded, but except for genuine globals (which need to be protected by a mutex, but shouldn't be used for just about anything) you're piping information from one state to another, which again doesn't have a synchronization problem.
If you have to manually mark a variable as "safe" every time you use it, you're just going to need to do that constantly for lots of variables. And you still are stuck getting the thread safety right: The above code isn't safe if something from another thread can change value. Which I guess is the point of the exclamation points, but ... somehow I prefer the Rust approach of "Mark this block of code as unsafe." Not that I've used either language.
The fact that it works correctly as a parameter, though -- that does hit the 80% case, at least.
> If you have to manually mark a variable as "safe" every time you use it, you're just going to need to do that constantly for lots of variables.
> ...
> The fact that it works correctly as a parameter, though -- that does hit the 80% case, at least.
That's the thing, it's not "80% of the cases". It actually hits just about all of them. For this whole situation to be a problem, rather than the compiler preventing bugs, you need all of the following:
Not all languages need to be good at everything. For my purposes, I welcome a language that makes working in heavily-multithreaded environments a bit easier, and I won't begrudge languages targeting other people's use cases either.
That's the thing, it's not "80% of the cases". It actually hits just about all of them. For this whole situation to be a problem, rather than the compiler preventing bugs, you need all of the following:
— You need a `var`, rather than a `val` (constant).
— The `var` needs to be nullable.
— The var is either a module global, or a class field.
— Your code doesn't touch anything multithreaded.
This includes not using NIO, or having any callbacks at
all to libraries that might use threading internally.
If any of those requirements is missing, the compilation error is either legitimate, or gone. In my experience, eliminating the multithreaded part eliminates all the reasonable use cases for Kotlin-as-a-Java-replacement: Android, web development, desktop applications.Not all languages need to be good at everything. For my purposes, I welcome a language that makes working in heavily-multithreaded environments a bit easier, and I won't begrudge languages targeting other people's use cases either.
Module global variables, I think we can agree, are less than ideal.
But I was thinking of class fields. I don't really want my compiler to hold my hand with respect to "nullable", if I know for a fact that, by the time the code gets there, it can't possibly be null.
Think of it this way: It's not a compile error to access an int from multiple threads, right? It doesn't require that !! syntax if you're just accessing a mutable class field or variable? So if I test an int for a value and then do something that assumes the value is still the same, I'm writing something that could crash just as easily as the null reference -- and array out-of-bounds, or a flag that says it's OK to do something but that flag changes after I check it, because I didn't grab a mutex.
So why does it add an error condition if you're accessing a mutable variable that's available in multiple threads just because it's nullable? Seems like extra busy work for no clear benefit for what is, as you say, a rather rare situation.
If we're going to share data across threads, I'd rather the language not present a false sense of security as a result of the !! operator.
But I was thinking of class fields. I don't really want my compiler to hold my hand with respect to "nullable", if I know for a fact that, by the time the code gets there, it can't possibly be null.
Think of it this way: It's not a compile error to access an int from multiple threads, right? It doesn't require that !! syntax if you're just accessing a mutable class field or variable? So if I test an int for a value and then do something that assumes the value is still the same, I'm writing something that could crash just as easily as the null reference -- and array out-of-bounds, or a flag that says it's OK to do something but that flag changes after I check it, because I didn't grab a mutex.
So why does it add an error condition if you're accessing a mutable variable that's available in multiple threads just because it's nullable? Seems like extra busy work for no clear benefit for what is, as you say, a rather rare situation.
If we're going to share data across threads, I'd rather the language not present a false sense of security as a result of the !! operator.
I think you misunderstood me — the rare situation is the compiler complaining about valid code. In practice, I find null handling in Kotlin to be quite straightforward, and it simplifies both writing and reading code a fair bit. I also find that forcing me to opt in to allowing nulls and, therefore, complicating my code a bit (by correctly handling them in the implementation) forces me to clean up my designs a bit.
Now, Jetbrains don't claim to solve data races in general, so it's fine to not solve all of them. But they _do_ claim to provide null safety in general (modulo java interop), so they _must_ provide null safety in the face of data races. I do agree that a language that solves data races in general is very, very valuable (and that's part of the reason why I've invested some time learning Rust), but solving nulls better than the half-arsed solution present in java Optional is, IMO, still quite valuable.
Finally, what do you mean by "a false sense of security as a result of the !! operator"? The !! operator is effectively "I know for a fact this isn't null, stop bothering me", and, when you read it, it should raise alarm bells, rather than make you feel safe. I also object to the notion that a language that is a bit safer is bad because it lulls you into a false sense of security. That's like saying that GCs are bad because they lull you into a false sense of security from memory leaks.
Now, Jetbrains don't claim to solve data races in general, so it's fine to not solve all of them. But they _do_ claim to provide null safety in general (modulo java interop), so they _must_ provide null safety in the face of data races. I do agree that a language that solves data races in general is very, very valuable (and that's part of the reason why I've invested some time learning Rust), but solving nulls better than the half-arsed solution present in java Optional is, IMO, still quite valuable.
Finally, what do you mean by "a false sense of security as a result of the !! operator"? The !! operator is effectively "I know for a fact this isn't null, stop bothering me", and, when you read it, it should raise alarm bells, rather than make you feel safe. I also object to the notion that a language that is a bit safer is bad because it lulls you into a false sense of security. That's like saying that GCs are bad because they lull you into a false sense of security from memory leaks.
> Tracing value types through a function is a solved problem.
Sure, i think that(type inference/flow typing) is solved in kotlin too. Sorry, i don't get the issue here. The last return value is throwing a compile time error because it breaks the function contract, which expects a non null value.
Sure, i think that(type inference/flow typing) is solved in kotlin too. Sorry, i don't get the issue here. The last return value is throwing a compile time error because it breaks the function contract, which expects a non null value.
> How can we protect ourselves from something that is beyond the language and is not controlled by it? There’s no way to do it.
There are plenty of ways to do it. The most popular is probably to capture the Java nullability at the border when Java values enter Kotlin code. From that point on, your code is null correct and will never give you an NPE.
> The Smart cast to ‘Int’ is impossible, because ‘value’ is a mutable property that could have been changed by this time error is driving me nuts. I want to kill someone or break something.
Yeah, lash at the compiler for pointing out your code is incorrect, right? Easy solution: use a `val` (which should be your default anyway). Another solution, although less easy: write better code.
There are plenty of ways to do it. The most popular is probably to capture the Java nullability at the border when Java values enter Kotlin code. From that point on, your code is null correct and will never give you an NPE.
> The Smart cast to ‘Int’ is impossible, because ‘value’ is a mutable property that could have been changed by this time error is driving me nuts. I want to kill someone or break something.
Yeah, lash at the compiler for pointing out your code is incorrect, right? Easy solution: use a `val` (which should be your default anyway). Another solution, although less easy: write better code.
You know a language has reached mainstream when it starts getting "sucks" articles written about it.
Reading the article I have thought twice and thrice about how to respond. Problem is every language can't be C/C++; it's like looking at LISP for first time and just diving into writing a blog ranting about it. Why does + comes before; hell = should have been assignment operator, why are things immutable and blah blah blah. It took me some time to develop taste, but all well engineered languages are tailored towards a mindset or problem-set. I might say few of points might be right here but language design is really really difficult and subjective topic. Try building the language yourself fixing stuff you just ranted about and I can guarantee you people ranting about the Frankenstein you will make.
It's entirely unclear to me whether this is parody or not.
I think author would have a hearth attack from Scala. It does not even have a conditional loops :-)
scala does not have a `continue`, `break` etc.. Kotlin allows that...
This seems like equivocation.
Fix your website on mobile. The width of the ads is larger than the actual content.
It's not just mobile, it's that way on desktops when you want to view it at a small width.
And if I max the screen... I still can't see all of the width of the code samples.
And if I max the screen... I still can't see all of the width of the code samples.
Interesting. What phone are you on? Could be because of a flexbox.
Nexus 9 tablet here. While in portrait the content takes up what I'd estimate to be 40% of the horizontal space, with the remainder used for ads and whitespace. The code samples are basically unreadable (requiring horizontal scrolling inside the div).
It's not so bad in landscape, however.
It's not so bad in landscape, however.
[deleted]
Here is a meta-comment that may seem irrelevant but I truly wonder about this odd tick of internet writing.
The title should be "Kotlin Sucks" or "Why I think Kotlin Sucks". The article doesn't say anything about the circumstances that lead to the sucking (e.g. the main developer suffered a serious brain injury midway through the project) he just lists and details aspects of it that suck.
This incorrect, misleading use of the word "why" in the titles of internet articles is so common I must ask, "why?"
The title should be "Kotlin Sucks" or "Why I think Kotlin Sucks". The article doesn't say anything about the circumstances that lead to the sucking (e.g. the main developer suffered a serious brain injury midway through the project) he just lists and details aspects of it that suck.
This incorrect, misleading use of the word "why" in the titles of internet articles is so common I must ask, "why?"
You've convinced me that "Why" in this title is a form of a clickbait—perhaps to appeal to our craving for causality—so we've removed it. Thanks!
Wow. You're welcome and thanks! I don't know why it should make me feel good that a stranger on the internet took action in response to my comment, but it does
My theory is that it's a mix of two things:
- Some guru years ago must have come up with some over-simplistic silver bullet such as: "if you claim to explain why something is the case in the title, everyone will be drawn in by the potential value you provide and the self-confidence you project". Or some shit like that (case in point - list "articles");
- Copying the linguistic mannerisms of some "in-crowd" makes you feel like you belong. This site is full of ticks like that if you pay attention.
- Some guru years ago must have come up with some over-simplistic silver bullet such as: "if you claim to explain why something is the case in the title, everyone will be drawn in by the potential value you provide and the self-confidence you project". Or some shit like that (case in point - list "articles");
- Copying the linguistic mannerisms of some "in-crowd" makes you feel like you belong. This site is full of ticks like that if you pay attention.
> he just lists and details aspects of it that suck
it's possession of said aspects aren't why it sucks?
it's possession of said aspects aren't why it sucks?
Learn the collections library. https://kotlinlang.org/docs/reference/collections.html All loops can be written in terms of them. Also because of inlining they have have the same runtime costs as hand written for loops.
"Smart cast to ‘Int’ is impossible, because ‘value’ is a mutable property that could have been changed by this time"
Smart casts only work on immutable values because of concurrency. If the variable hast to be mutable use ?.let { ... } instead of smart casts.
What's wrong with two exclamation points? If you're using it you are probably doing something wrong anyway.
When getting a type from Java that hasn't been annotated as @NonNull or @Nullable it is regarded a platform type. This means it's programmers responsibility to check if it can be null. Same as in Java or any other language that doesn't have nullable types.
Assignment is not an expression. We've written 20K of production Kotlin code without this ever being a useful convention. Assignment being an expresssion is layover from the C family of languages. If you really want C's syntax and semantics, go write C.
The ?: operator is great when chaining a bunch of operations all of which could return a null which we want to convert to a default. Here is an example: claimData?.claimTimeMinutes?.toLong() ?: 5L
Generics in Kotlin have the same issues as Java because of type erasure. It does offer a few improvements with the use of reified type variables.
Lastly, if you want to have descriptive structures use data classes.
This guy just wants to program in C. If it's not C it's crap according to the article.