分号推理的规则是什么?

What are the rules of semicolon inference?

Kotlin provides “semicolon inference”: syntactically, subsentences (e.g., statements, declarations etc) are separated by the pseudo-token SEMI, which stands for “semicolon or newline”. In most cases, there’s no need for semicolons in Kotlin code.

grammar 页面就是这么说的。这似乎暗示在某些情况下需要指定分号,但它没有指定分号,下面的语法树也没有明确说明这一点。另外我怀疑在某些情况下此功能可能无法正常工作并导致问题。

所以问题是什么时候应该插入分号以及需要注意哪些极端情况以避免编写错误代码?

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

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

分号的两种常见情况:

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

enum class Things {
    ONE, TWO;

    fun isOne(): Boolean = this == ONE
}

并且在这种情况下,如果您没有正确执行,编译器将直接告诉您:

Error:(y, x) Kotlin: Expecting ';' after the last enum entry or '}' to close enum class body

否则,唯一的其他常见情况是当您在同一行上执行两个语句时,也许是为了简洁起见:

myThingMap.forEach { val (key, value) = it; println("mapped $key to $value") } 

最后一个例子中没有分号会给你一个更神秘的错误,让你混淆你在做什么。很难编写一些既有效的代码,因为用分号分隔的两个语句在删除分号并成为一个时也有效。

过去还有其他情况,比如 class 的初始化块在 Kotlin 1.0 之前更“匿名”{ ... },后来变成 init { ... },不再需要分号,因为它更清晰。这些案例不再保留在语言中。

对该功能的信心:

Also I have suspicions that there are some cases where this feature may not work correctly and cause problems.

该功能运行良好,没有任何证据表明此功能存在问题,并且多年的 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() }
    }
}

在这种情况下,doStuff 被分配给 whatever("message") { doNothing() } 的调用结果,它是类型 ()->Unit 的函数;如果你添加一个分号,它就会被分配给函数{ doNothing() },它也是()->Unit类型的。所以代码是双向有效的。 但我还没有看到这样的事情自然发生,因为一切都必须完美排列。 feature suggested emit keyword or ^ hat operator 会使这种情况变得不可能,并且由于强烈反对的意见和时间限制,它被考虑但在 1.0 之前被放弃。

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

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

没有分号,Kotlin 认为 { doNothing() } 语句是 println() 的第二个参数,编译器会报错。

Kotlin 似乎主要 急切地推断分号。似乎有例外(如 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.
}

更常见的情况可能是以下情况,我们在下一行中有一个 return 和表达式。大多数时候类型系统会抱怨没有 return 值,但是如果 return 类型是 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.
 }

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