Kotlin中的分号推断规则是什么?

Mal*_*olm 12 syntax grammar kotlin

Kotlin提供"分号推断":在语法上,子句(例如,语句,声明等)由伪令牌SEMI分隔,其代表"分号或换行符".在大多数情况下,Kotlin代码中不需要分号.

这就是语法页面所说的内容.这似乎暗示在某些情况下需要指定分号,但它没有指定它们,并且下面的语法树并不能完全证明这一点.此外,我怀疑在某些情况下此功能可能无法正常工作并导致问题.

所以问题是应该何时插入分号以及需要注意哪些角落情况以避免编写错误的代码?

Jay*_*ard 20

您只需要在编译器与您尝试执行的操作不明确的情况下指定分号,并且缺少分号将导致明显的编译器错误.

规则是:不要担心这一点,根本不使用分号(除了以下两种情况).编译器会在你弄错的时候告诉你,保证.即使您不小心添加了一个额外的分号,语法突出显示也会显示"冗余分号"的警告是不必要的.

分号的两种常见情况:

具有枚举列表以及枚举中的属性或函数的枚举类需要;在枚举列表之后,例如:

enum class Things {
    ONE, TWO;

    fun isOne(): Boolean = this == ONE
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,编译器会直接告诉您是否未能正确执行此操作:

错误:(y,x)Kotlin:期待';' 在最后一次枚举输入之后或'}'关闭枚举类本体

否则唯一的另一种常见情况是当你在同一行上做两个语句时,可能为了简洁起见:

myThingMap.forEach { val (key, value) = it; println("mapped $key to $value") } 
Run Code Online (Sandbox Code Playgroud)

在最后一个例子中没有分号会给你一个更加神秘的错误,因为它让你感到困惑.很难使一些代码既有效又可用作分号分隔的两个语句,当分号被删除并且它们成为一个分号时也是有效的.

在过去,还有其他一些案例,例如类的初始化块,{ ... }在Kotlin 1.0之前更加"匿名" ,后来变得init { ... }不再需要分号,因为它更清晰.这些案件不再保留在该语言中.

对此功能的信心:

此外,我怀疑在某些情况下此功能可能无法正常工作并导致问题.

该功能运行良好,没有任何证据证明此功能存在任何问题,多年的Kotlin经验未发现此功能适用的任何已知情况.如果缺少问题,;编译器将报告错误.

搜索我的所有开源Kotlin以及我们内部相当大的Kotlin项目,我发现除了上述情况之外没有分号 - 而且总共非常少.支持"不要在Kotlin中使用分号"的概念作为规则.

但是,您可以有意设计编译器不报告错误的情况,因为您创建的代码有效且具有不同分数的含义.这看起来如下(@Ruckus的答案的修改版本):

fun whatever(msg: String, optionalFun: ()->Unit = {}): () -> Unit = ...

val doStuff: () -> Unit = when(x) {
    is String -> {
        { doStuff(x) }
    }
    else -> { 
        whatever("message") // absence or presence of semicolon changes behavior
        { doNothing() }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,doStuff被赋予调用的结果,该调用 whatever("message") { doNothing() }是类型的函数()->Unit; 如果你添加一个分号,它将被赋予{ doNothing() }同样类型的函数()->Unit.因此代码在两个方面都是有效的. 但我没有看到这样的事情自然发生,因为一切都必须完美排列.该功能建议emit的关键字或^帽子运营会作出这种情况下是不可能的,它被认为但由于强烈oposed意见和时间的限制降至1.0之前.

  • 我已经看到它自然发生。我坚持在我们的 kotlin 代码中使用分号,因为代码质量/正确性/可读性应该始终优先于美观。省略分号没有任何好处。 (4认同)
  • 确实“自然发生”的这个示例怎么样:https://android.jlelse.eu/why-i-missed-semi-colons-today-e2fb136f58e5 (3认同)

Ruc*_*oom 8

除了Jayson Minard的回答之外,我还遇到了另一个奇怪的边缘情况,需要一个分号.如果您在不使用return语句的情况下返回函数的语句块中,则需要使用分号.例如:

val doStuff: () -> Unit = when(x) {
    is String -> {
        { doStuff(x) }
    }
    else -> { 
        println("This is the alternate");  // Semicolon needed here
        { doNothing() }
    }
}
Run Code Online (Sandbox Code Playgroud)

没有分号,Kotlin认为该{ doNothing() }语句是第二个参数,println()编译器报告错误.

  • 非常感谢,这正是我所寻找的错误类型!这就是为什么当有人说推理始终正常时,我通常会非常谨慎.Jayson Minard似乎对我提到的JavaScript有点过分注意,这是Kotlin中类似问题如何表现的一个很好的例子,尽管程度要小得多. (2认同)

Flo*_*sch 5

科特林似乎主要是热切地推断分号.似乎有例外(如Jayson Minard的枚举示例所示).

通常,类型系统将捕获严重推断的分号,但是在某些情况下编译器会失败.

如果调用的参数在下一行(包括括号)中,Kotlin将假定参数只是一个新的带括号的表达式语句:

fun returnFun() : (x: Int) -> Unit {
  println("foo")
  return { x -> println(x) }
}

fun main(args: Array<String>) {
    println("Hello, world!")
    returnFun()
       (1 + 2)    // The returned function is not called.
}
Run Code Online (Sandbox Code Playgroud)

更常见的情况可能是以下情况,我们在下一行中返回表达式.大多数情况下类型系统会抱怨没有返回值,但如果返回类型是Unit,那么所有的赌注都是关闭的:

fun voidFun() : Unit {
    println("void")
}

fun foo() : Unit {
    if (1 == 1) return 
    voidFun()  // Not called.
 }

fun bar() : Unit {
    if (1 == 1)
        return 
            voidFun()  // Not called.
 }
Run Code Online (Sandbox Code Playgroud)

bar如果return voidFun()不适合一行,该功能可能会发生.也就是说,开发人员必须简单地在单独的行上写入对函数的调用.