分号推断的规则是什么?

时间:2016-09-04 15:19:57

标签: kotlin syntax grammar

  

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

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

所以问题是什么时候应该插入一个分号?为了避免编写错误的代码,需要注意哪些角落情况?

3 个答案:

答案 0 :(得分:16)

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

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

分号的两种常见情况:

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

enum class Things {
    ONE, TWO;

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

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

  

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

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

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

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

过去还有其他一些情况,例如类的初始化块,在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() }
    }
}

在这种情况下,会为doStuff分配调用whatever("message") { doNothing() }的结果,()->Unit是类型{ doNothing() }的函数;如果添加分号,则会为其分配函数()->Unit,该函数的类型为serverName # more alarmsScript.sh #!/bin/bash now=$(date +"%Y%m%d%H%M") before=$(date -d '1 hour ago' +"%Y%m%d%H%M") /opt/SMAW/INTP/bin/AdvErrLogViewer.pl -s `echo $before` -e `echo $now` -m 'all' '/advdata/trace/AdvErrorTrace/CCC_USER_*' | grep -B 2 -A 2 -i major >/dump/stats/IN-$now.txt 。因此代码在两个方面都是有效的。 但我没有看到这样的事情自然发生,因为一切都必须完美排列。 feature suggested emit keyword or ^ hat operator会使这个案例变得不可能,并且由于强烈反对意见和时间限制而被考虑但在1.0之前被删除。

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:5)

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.
}

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

如果bar不适合一行,return voidFun()功能可能会发生。也就是说,开发人员只需在单独的行上编写对函数的调用。