Scala 3 缩进语法详解:lampepfl/dotty 项目中的新特性

Scala 3 缩进语法详解:lampepfl/dotty 项目中的新特性

dotty The Scala 3 compiler, also known as Dotty. dotty 项目地址: https://gitcode.com/gh_mirrors/do/dotty

前言

Scala 3 引入了一套全新的缩进语法规则,这是对传统基于大括号语法的重要补充。本文将深入解析 Scala 3 中的缩进规则、可选大括号机制以及相关的语法特性,帮助开发者更好地理解和运用这一新特性。

缩进规则概述

Scala 3 对代码缩进实施了两条核心规则,违反这些规则会导致编译器警告:

  1. 大括号区域对齐规则:在大括号界定的区域内,任何语句都不能比该区域第一个语句的起始位置更靠左。

    示例错误:

    if (x < 0) {
      println(1)
      println(2)
    
    println("done")  // 错误:缩进太靠左
    
  2. 遗漏大括号检测规则:当缩进显著关闭时(如在 Scala 2 模式或使用 -no-indent 标志),如果表达式的一个缩进子部分以换行结束,则下一个语句的缩进必须小于该子部分。

    示例错误:

    if (x < 0)
      println(1)
      println(2)   // 错误:缺少 `{`
    

可选大括号机制

Scala 3 编译器会在特定换行处插入 <indent><outdent> 标记,这些标记在语法上等同于大括号 {}

缩进算法原理

编译器使用一个缩进宽度栈 IW 来管理缩进层级,初始时栈中包含一个零缩进宽度元素。当前缩进宽度即栈顶元素的缩进宽度。

插入 <indent> 的规则

在换行处插入 <indent> 的条件:

  • 当前位置可以开始一个缩进区域
  • 下一行第一个标记的缩进宽度严格大于当前缩进宽度

可以开始缩进区域的位置包括:

  • extension 的主要参数之后
  • with 之后(在给定实例中)
  • 模板体开头的 : 之后
  • 特定标记之后:=, =>, ?=>, <-, catch, do, else, finally, for, if, match, return, then, throw, try, while, yield
插入 <outdent> 的规则

在换行处插入 <outdent> 的条件:

  • 下一行第一个标记的缩进宽度严格小于当前缩进宽度
  • 上一行最后一个标记不是表示语句继续的标记(如 then, else, do 等)
  • 如果是前导中缀操作符,其缩进宽度必须小于当前缩进宽度

模板体的可选大括号

Scala 3 允许省略类、特质或对象定义周围的大括号,改用冒号和缩进来界定模板体。

示例:

trait A:
  def f: Int

class C(x: Int) extends A:
  def f = x

语法上,: 后跟缩进块等同于用大括号包围的内容。

方法参数的可选大括号

从 Scala 3.3 开始,在期望函数参数的位置也可以使用冒号语法:

times(10):
  println("ah")
  println("ha")

还支持在同一行后跟 lambda 参数部分:

xs.map: x =>
  val y = x - 1
  y * y

空格与制表符的处理

缩进前缀可以由空格和/或制表符组成。为避免错误,建议不要在同一个源文件中混合使用空格和制表符。

缩进与大括号的混合使用

缩进可以自由地与 {...}[...](...) 混合使用:

  1. 大括号多行区域的假设缩进宽度是开大括号后第一个新行标记的缩进宽度
  2. 括号或方括号内的多行区域:
    • 如果开括号在行尾,则采用跟随标记的缩进宽度
    • 否则采用封闭区域的缩进宽度
  3. 遇到闭括号时,会插入足够的 <outdent> 来关闭所有嵌套的缩进区域

case 子句的特殊处理

match 表达式和 catch 子句的缩进规则有特殊调整:

  • 如果 case 出现在与 match 本身相同的缩进级别,也会打开缩进区域
  • 缩进区域在第一个非 case 标记或更小缩进的标记处关闭

示例:

x match
case 1 => print("I")
case 2 => print("II")

使用缩进表示语句延续

缩进用于决定是否在两行之间插入虚拟分号:

  • 如果第二行比第一行缩进更多,且以 (, [, { 开头,或第一行以 return 结尾,则抑制虚拟分号插入

示例:

f(x + 1)
  (2, 3)        // 等同于 `f(x + 1)(2, 3)`

结束标记(End Marker)

为解决大缩进区域难以辨识结束点的问题,Scala 3 引入了可选的 end 标记:

def largeMethod(...) =
  ...
end largeMethod

何时使用结束标记

建议在以下情况使用结束标记:

  • 构造包含空行
  • 构造较长(15-20行或更多)
  • 构造结束时有深度缩进(4个缩进级别或更多)

示例:缩进宽度实现

以下是一个缩进宽度具体表示的示例实现:

enum IndentWidth:
  case Run(ch: Char, n: Int)
  case Conc(l: IndentWidth, r: Run)

  def <= (that: IndentWidth): Boolean = this match
    case Run(ch1, n1) =>
      that match
        case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0)
        case Conc(l, r)   => this <= l
    case Conc(l1, r1) =>
      that match
        case Conc(l2, r2) => l1 == l2 && r1 <= r2
        case _            => false

总结

Scala 3 的缩进语法为代码结构提供了更灵活的表达方式,同时保持了与现有大括号语法的兼容性。通过合理使用缩进规则和可选大括号机制,开发者可以编写出更简洁、更易读的代码。理解这些规则的工作原理有助于避免常见的缩进错误,并充分利用 Scala 3 的新特性。

dotty The Scala 3 compiler, also known as Dotty. dotty 项目地址: https://gitcode.com/gh_mirrors/do/dotty

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卫标尚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值