Hacker News new | ask | show | jobs
by apatrida 2953 days ago
A comment I posted on that blog post, that likes to keep going to spam box.

1. Name shadowing is a compiler warning. It is good to pay attention and clear all warnings by resolving the issues. It isn't a secret when you name shadow, and a developer I would hope would pay enough attention to not do it on accident. I've used Kotlin since 2013 and don't think I've shadowed a name even one time on accident. There is an issue in the Kotlin tracker to allow turning specific warnings into errors, this would give you the behaviour you want once added.

2. Kotlin type inference is the best out there, nothing compares in accuracy and scope. You aren't getting the same thing from Java 10 that Kotlin has by far.

3. Compile time null safety is accurate from Java libraries if they inform as to their null safety, for example there are many libraries that use `@NotNull ` and `@Nullable ` annotations and Kotlin respects and enforces those. They also handle this for JDK calls so that all standard library calls are correctly interpreted. You can fix your own custom Java libraries which also helps with Java static analysis. And eventually you'll use more and more calls to your own Kotlin code or to Kotlin libraries and you'll really be glad all of this null safety is there and part of the compiler.

4. Class literals are many times not used in Kotlin API design. Instead you as an API designer would just write an extension function that reifies the generic parameter and auto infers the class literal. Or you can do it yourself to extend any API you wish. You will only do that once and then enjoy the magic a thousand times after. There are actually modules for things like Jackson and GSON that do this already. So then the syntax is better than the Java because you don't have that parameter at all (which is type erased whereas reifying the full generic type is not). Here is an example of Java vs. Kotlin using Jackson object mapper in the same use case you were demonstrating:

Java:

  final MyStateObject state = mapper.readValue(json, MyStateObject.class)  // type erased, which sometimes is ok
  final List<MyStateObject> states =  mapper.readValue(json, new TypeReference<List<MyStateObject>>()); // not type erased
 
Kotlin:

  val state: MyStateObject        = mapper.readValue(json) // not type erased
  val states: List<MyStateObject> = mapper.readValue(json) // not type erased
 
Alternative Kotlin:

  val state  = mapper.readValue<MyStateObject>(json)       // not type erased
  val states = mapper.readValue<List<MyStateObject>>(json) // not type erased
 
Isn't type inference wonderful? Especially with reified types making API's like this possible.

5. Type declarations in the form of `name: Type ` are by design to allow for unambiguous syntax which keeps the language smaller and leaner than the C style typing which cannot be used everywhere without extra cruft to delimit what is going on. Plus this is more readable when scanning variable declarations since they are at a fixed position from the left instead of a random position on the right. Here are some good reading for you that let you know the Java style `Type varName ` is actually the less common across languages: https://softwareengineering.stackexchange.com/a/311739/20888... and then this post about how it interferes with higher level concepts in language to have it backwards as Java does: https://softwareengineering.stackexchange.com/a/311730/20888...

6. Companion objects are there so they can be extended, including with extension functions. You can easily do things like logging using other models and delegates instead of adding a companion object. Your logger system likely caches the logger by the way so having a reference on the object is not a huge item to add. You can also have a `main()` as a top level function and is not required to be in a class which probably makes more sense for a main anyway.

7. Collection literals are not present in Kotlin because in the JVM developers are more specific about the exact type of collection they wish to construct, and there are simple helper functions which make things clearer. The `: ` being used for maps would overuse that symbol and cause it to have different meanings (I think you complained about two uses of `: ` then you later suggest a third). What is wrong with `to ` for creating a pair which is used to create the map? it is an extension function, highly readable, and you can actually make your own with some other name (other than `: `) `to ` isn't part of the language syntax nor forced upon you. The Groovy syntax is bad because it looks like you are declaring a list/array. The other syntaxes don't say whether the maps are readonly or mutable and that is important in Kotlin. It is a carefully thought through issue.

8. Maybe? is not needed as much due to alternatives and null safety. But, there is the JDK Optional which you are showing and can just as easily be used by Kotlin as well. And more "native" versions in open-source Kotlin libraries. Kotlin just doesn't have one in its standard library, nor does it need one. I personally dislike API designs that force optionals on me for everything, so I'm glad Kotlin doesn't.

For your example code, why are you using Optional for this case of `parseAndInc `? Kotlin has the `?. ` safe operator to continue a chain when not null, and the `?: ` Elvis operator to provide a fallback on null values. Any operator like `+ ` is also available from its operator function (i.e. `plus() `) or in this case simply `inc() `. Therefore:

Kotlin:

  fun parseAndInc(possibleNumber: String?) = possibleNumber?.toIntOrNull()?.inc() ?: 0
 
Yet you can also write the same Kotlin code as your Java example (and slightly less typing):

Also Kotlin:

  fun parseAndInc(number: String): Int {
    return Optional.ofNullable(number)
        .map(Integer::parseInt)
        .map { it + 1 }
        .orElse(0)
  }
 
Compared to your Java:

  public int parseAndInc(String number) {
    return Optional.ofNullable(number)
                   .map(Integer::parseInt)
                   .map(it -> it + 1)
                   .orElse(0);
  }
 
Please remember that everything you can do in Java you can do in Kotlin including using all those Java classes you are familiar with. But, you can also learn idiomatic Kotlin and write it differently. This is one of the reasons the learning curve is very low.

9. Data classes, so you aren't complaining here or are you? You can't inherit the equivalent in Java either, you would have to rewrite `equals `, `hashCode `, `toString ` and more if you did that and each would need to be customized. So Kotlin has the same issue, inheritance of these is dangerous because manual decisions need to be made in order to do so. You CAN inherit from another class, and you could use composition and interfaces instead. Data classes are a life saver in code, covering a huge number of cases without all the boilerplate of manually coded JavaBeans.

10. Final classes by default are a good thing, and this has been heavily researched and talked about in the Java community as a best practice. Some old Java libraries do not know how to handle this because they don't expect it to be a problem. But those libraries are changing, and complier plugins for these libraries already help you out with 0 effort in changing code. Plus, newer libraries deal with this without problems. If you want to modernize, you have to modernize and not just expect new things to continue with allowing bad habits because some legacy code doesn't do things elegantly. I can't think of any blocking case related to this. It was well known, well covered, and is a non-issue.

11. Shallow learning curve. Kotlin is small language, consistent, and elegant. It does NOT have a steep learning curve. It is no where near Scala in terms of effort and complexity, yet you lump it in there anyway. Plus, isn't it worth learning a language over a week that'll save you 40-50% the lines of code, automatically eliminate whole categories of bugs you write, be many times more readable and maintainable? Kotlin is a few days to a week for an ex Java developer to write reasonable code, and then later they'll write more elegant code over time.

And about Spock vs. Spek: You don't have to pick either/or, since you can use Spock from Kotin too like any other Java library.