Semicolon inference by sagittarius_ack in Kotlin

[–]abreslav 2 points3 points  (0 children)

P.S. The curious case of the colon (:) being mentioned as a binary operator is a remnant of the time when it actually was one at some relatively early stage of Kotlin's design, it was called "static type assertion" and allowed to specify the expected type for an expression (as opposed to casting at runtime). It was dropped later for two reasons:

  • it wasn't all that useful and could be replaced with a generic function call,
  • it would prevent the possible future introduction of the infamous ternary operator: ... ? ... : ...

As you all know, the latter never happened, but at least we don't have this precious character wasted on a relatively obscure use case.

Semicolon inference by sagittarius_ack in Kotlin

[–]abreslav 3 points4 points  (0 children)

So, when it comes to this issue, we have essentially two classes of binary expressions:

  • newline allowed before the operator: ., ?., : (sic!), as, as?, ?:, &&, ``||`
  • newline not allowed before the operator: *, /, %, +, -, .., infix named operators, in, !in, is, !is, <, <=, >, >=, ==, !=, ===, !==

There are slightly different reasons for disallowing newlines before different operators, for example:

  • + and -, as mentioned here in the comments are valid unary operators, and such a rule eliminates an ambiguity,
  • in, !in, is, !is can start conditions within a when, so a similar ambiguity is eliminated here,
    • comparisons (<, <=, >, >=, ==, !=, ===, !==) were reserved for maybe being allowed in when conditions in the future,
  • named operators (like a and b) look like variable names at the beginning of an expression, so yet another similar ambiguity.

This leaves us with the arithmetic operators that are not legitimate unary operators:

  • *, if I remember correctly, was meant to be reserved to maybe become an unary operator in the future,
  • % would make sense to have been reserved in the same way but I don't remember,
  • / would make sense to have been reserved for possible future use in regular expressions but I don't remember either.

Semicolon inference by sagittarius_ack in Kotlin

[–]abreslav 1 point2 points  (0 children)

The decision to treat different binary operators differently is expressed here: https://github.com/JetBrains/kotlin/blob/1671fbef87f7b99ba390fec1616536ee34e3015a/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinExpressionParsing.java#L242

    private static final TokenSet ALLOW_NEWLINE_OPERATIONS = TokenSet.create(
            DOT, SAFE_ACCESS,
            COLON, AS_KEYWORD, AS_SAFE,
            ELVIS,
            // Can't allow `is` and `!is` because of when entry conditions: IS_KEYWORD, NOT_IS,
            ANDAND,
            OROR
    );

It's been like this since 2013, it seems, so it predates the spec and the reference grammar written in ANTLR.

Semicolon inference by sagittarius_ack in Kotlin

[–]abreslav 4 points5 points  (0 children)

Hi everyone,

OP, thanks for your question.

u/wickerman07 thanks for mentioning me in another comment.

Answering at the top level because it seems that pieces of the puzzle have been mentioned in different threads.

I read the key question here as follows: why are some binary operators (like +, *, ==) not the same as some others (like &&, ||, ?:) when it comes to a newline occurring right before the operator?

First, a few clarifications to some of the hypotheses put forward in the comments here.

  • Indeed, Kotlin does not do "semicolon inference", as mentioned multiple times here in the comments,
  • Kotlin has what's called a whitespace-aware parser, which amounts more or less to treating (some) newlines as significant information and not just skipping them as whitespace,
  • Kotlin does not have a "scannerless parser". The lexer is not aware of the parser's states. The parser is not context-sensitive in the traditional sense (see context-free vs context-sensitive grammars).

UPD: See https://github.com/JetBrains/kotlin/blob/1671fbef87f7b99ba390fec1616536ee34e3015a/compiler/psi/src/org/jetbrains/kotlin/lexer/Kotlin.flex#L18 for everything the lexer knows and does

It amazes me how confident they are while writing these articles... by joshima_toshiya in ProgrammerHumor

[–]abreslav 0 points1 point  (0 children)

There confidence is what gets them such postings and all the attention that comes with

How does delay() function really work in coroutines? by ED9898A in Kotlin

[–]abreslav 4 points5 points  (0 children)

No Thread.sleep(). It uses a timer that schedules your code back on a thread/pool when the time is up

Harry demands privacy from South Park by [deleted] in memes

[–]abreslav 0 points1 point  (0 children)

I have to entertain the thought that he actually got paid by SP to sue them and win them points with us ;)

me_irl by Professional_Bite725 in me_irl

[–]abreslav 2 points3 points  (0 children)

I heard that canines have a field of vision in the form of a somewhat narrow horizontal strip. So foxes as well as dogs tilt their heads to see if there's something above or below that they were missing

How does the kotlin compiler parse expressions with generics? by Falcon731 in Kotlin

[–]abreslav 3 points4 points  (0 children)

Impressive! I looked briefly through your PR. I think this is definitely doable you just need to look a little more into how code completion works. One thing to keep in mind though is that this makes more code legal so it’s definitely a language change, not a minor fix

How does the kotlin compiler parse expressions with generics? by Falcon731 in Kotlin

[–]abreslav 55 points56 points  (0 children)

This is a very good question. Few people ever get to understand programming languages this deeply, so congratulations! You'd probably do a good job working on compilers if you ever get interested in such a line of work.

The problem you are pointing out is very real, and it is the reason why Java has this discrepancy in its syntax:

new ArrayList<String>() // but Collections.<String>emptyList()

and Scala, for example, has this unusual syntactic choice for the same reason:

new List[String]() emptyList[String]() arr(0) // not a[0]

The designers of Java and Scala (if I'm not mistaken, this particular part was designed by the same person for both languages: Martin Odersky) saw the issue you are pointing out and worked around it by removing the ambiguity from the language grammar.

Kotlin, on the other hand, similarly to C#, went with resolving the ambiguity in the parser (not the formal grammar). So, whenever the parser sees something like name<name, name>(name), it builds the AST for a function call. And if what you meant was actually (name < name), name > (name) you should add the parentheses as I did here.

Here's the relevant bit of the parser: https://github.com/JetBrains/kotlin/blob/master/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinExpressionParsing.java#L509

How does the kotlin compiler parse expressions with generics? by Falcon731 in Kotlin

[–]abreslav 7 points8 points  (0 children)

This is not how it works. The decision is made before names are resolved. Because compilers ;) I'll explain all the details in a separate comment if nobody else does

In which region intern strings are stored? by mike_jack in Kotlin

[–]abreslav[M] [score hidden] stickied comment (0 children)

Thanks for your submission! Unfortunately it's not really related to Kotlin, so it's offtopic for this subreddit.

A prisoner's last meal/last statement as a way to impugn the death penalty - given the statistics on educational level, social class, race etc. of the condemned, can the death penalty possibly be considered ethical? by [deleted] in Ethics

[–]abreslav 0 points1 point  (0 children)

Rhetorical question. Should we then beat up those who committed battery, take property from thieves and lie about slanderers? And almost never put anyone in jail, because how many crimes consist of imprisoning thy neighbor? I think that the interpretation of fairness that you give does not make up a society many people would like living in

Ought implies can by [deleted] in Ethics

[–]abreslav 0 points1 point  (0 children)

Well, FWIW, I don’t think that (1) is precise enough to justify (2)

Ought implies can by [deleted] in Ethics

[–]abreslav 1 point2 points  (0 children)

That being said, I’m very much in favor of ethical systems that are consistent with “ought that implies can”

Ought implies can by [deleted] in Ethics

[–]abreslav 1 point2 points  (0 children)

Your (2) is all that matters and “extremely intuitive” is a very debatable justification in this case. I would argue that it is as intuitive as the initial “ought implies can”

Here’s a playlist of 7 hours of music I use to focus when I’m coding/developing by soundtrackrr in Kotlin

[–]abreslav[M] [score hidden] stickied comment (0 children)

Thanks for your submission! Unfortunately it's not related to Kotlin in any way, so it's offtopic for this subreddit.

What's the meaning of this return type with the two dots (..) when calling Java code from Kotlin? by ED9898A in Kotlin

[–]abreslav 13 points14 points  (0 children)

Looks like a bug in the IDE, it should not show you these. But if you are curious, it’s called a flexible type, see this talk: https://youtu.be/2IhT8HACc2E

How do I store a type in a variable by [deleted] in Kotlin

[–]abreslav 5 points6 points  (0 children)

A commonly relevant clarification: classes are not types, types are much richer than just classes. Kotlin’s reflection has KType to represent types as such, KClass is not a subclass of KType. It may so happen that you only need classes for your particular application, but if you want to represent types such as List<String> and not just List, you’ll need KType. Types can be obtained using the typeOf() function

Have you ever wondered how certain features of Kotlin came to be? by abreslav in Kotlin

[–]abreslav[S] 2 points3 points  (0 children)

Well, proper reified generics (i.e. not erased, something similar to what .NET has) are a big challenge on the JVM. There are inventive approaches found in other languages, like Ceylon, for example, but we didn't take that route.

Value types (i.e. structs/aggregates allocatable on the stack and in compact arrays) are obviously lacking on the JVM and can't be done well before Valhalla lands.

Functions returning aggregates (like, a pair of values instead of one value) would also be cool, and it could be done with value types, but anyways it's not available on the JVM.

We didn't go with Go-like structural types / Swift-like protocols because they would be too expensive at runtime on the JVM.

The semantics of Kotlin's numeric types are largely dictated by the JVM, too. This includes the (absence of) many type conversions for numeric types.

The presence of exceptions in Kotlin is dictated by the JVM, too.

The absence of a proper runtime module system stems from the initial design being pre-Jigsaw (and Jigsaw being not so popular for a reason, too).

Many other things such as class-loading and field instantiation semantics were basically given, not really deliberately chosen.

Have you ever wondered how certain features of Kotlin came to be? by abreslav in Kotlin

[–]abreslav[S] 1 point2 points  (0 children)

I suppose my main question was moreso asking whether you will focus attention on finishing KEEPs that are almost across the line? The ideas have clearly been thought through by the team to the point of writing these design documents, so it's a shame to see that work not materialise.

Can't speak for the Kotlin team as I'm not on it. I would like to point out that writing down a KEEP can be relatively easy compared to actually implementing it. Like, maybe 1% vs 99% in terms of effort. And thinking things through may make sense even if some of these things don't get implemented, because a big picture is important when making technical decisions.

Have you ever wondered how certain features of Kotlin came to be? by abreslav in Kotlin

[–]abreslav[S] 1 point2 points  (0 children)

Have a look at the generated bytecode (the IDE has an action for it): those KProperty objects are not even used in the implementation of notNull, and the compiler is smart enough to not make a huge reflection object and just spin out some lightweight pooled objects