在 GitHub 上编辑此页面

上下文函数

上下文函数是(仅)具有上下文参数的函数。它们的类型是上下文函数类型。以下是上下文函数类型的一个示例

import scala.concurrent.ExecutionContext

type Executable[T] = ExecutionContext ?=> T

上下文函数使用 ?=> 作为“箭头”符号编写。它们应用于合成的参数,与应用具有上下文参数的方法相同。例如

given ec: ExecutionContext = ...

def f(x: Int): ExecutionContext ?=> Int = ...

// could be written as follows with the type alias from above
// def f(x: Int): Executable[Int] = ...

f(2)(using ec)   // explicit argument
f(2)             // argument is inferred

相反,如果表达式 E 的预期类型是上下文函数类型 (T_1, ..., T_n) ?=> U 并且 E 尚未是上下文函数字面量,则 E 通过将其重写为以下内容转换为上下文函数字面量

(x_1: T1, ..., x_n: Tn) ?=> E

其中名称 x_1、...、x_n 是任意的。此扩展在对表达式 E 进行类型检查之前执行,这意味着 x_1、...、x_n 可用作 E 中的给定值。

与类型类似,上下文函数字面量使用 ?=> 作为参数和结果之间的箭头来编写。它们与普通函数字面量不同,因为它们的类型是上下文函数类型。

例如,继续使用前面的定义,

def g(arg: Executable[Int]) = ...

g(22)      // is expanded to g((ev: ExecutionContext) ?=> 22)

g(f(2))    // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))

g((ctx: ExecutionContext) ?=> f(3))  // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
g((ctx: ExecutionContext) ?=> f(3)(using ctx)) // is left as it is

示例:生成器模式

上下文函数类型具有相当大的表现力。例如,它们可以支持“生成器模式”,其目的是构建如下表格

table {
  row {
    cell("top left")
    cell("top right")
  }
  row {
    cell("bottom left")
    cell("bottom right")
  }
}

其思想是为 TableRow 定义类,允许通过 add 添加元素

import scala.collection.mutable.ArrayBuffer

class Table:
  val rows = new ArrayBuffer[Row]
  def add(r: Row): Unit = rows += r
  override def toString = rows.mkString("Table(", ", ", ")")

class Row:
  val cells = new ArrayBuffer[Cell]
  def add(c: Cell): Unit = cells += c
  override def toString = cells.mkString("Row(", ", ", ")")

case class Cell(elem: String)

然后,可以将 tablerowcell 构造函数方法定义为带有上下文函数类型作为参数,以避免不必要的管道样板。

def table(init: Table ?=> Unit) =
  given t: Table = Table()
  init
  t

def row(init: Row ?=> Unit)(using t: Table) =
  given r: Row = Row()
  init
  t.add(r)

def cell(str: String)(using r: Row) =
  r.add(new Cell(str))

有了该设置,上面的表格构建代码会编译并扩展为

table { ($t: Table) ?=>

  row { ($r: Row) ?=>
    cell("top left")(using $r)
    cell("top right")(using $r)
  }(using $t)

  row { ($r: Row) ?=>
    cell("bottom left")(using $r)
    cell("bottom right")(using $r)
  }(using $t)
}

示例:后置条件

作为一个更大的示例,这里提供了一种使用扩展方法 ensuring 定义用于检查任意后置条件的构造的方法,以便可以通过 result 简单地引用检查结果。该示例结合了不透明类型别名、上下文函数类型和扩展方法,以提供零开销抽象。

object PostConditions:
  opaque type WrappedResult[T] = T

  def result[T](using r: WrappedResult[T]): T = r

  extension [T](x: T)
    def ensuring(condition: WrappedResult[T] ?=> Boolean): T =
      assert(condition(using x))
      x
end PostConditions
import PostConditions.{ensuring, result}

val s = List(1, 2, 3).sum.ensuring(result == 6)

说明

我们使用上下文函数类型 WrappedResult[T] ?=> Boolean 作为 ensuring 条件的类型。因此,ensuring 的参数(例如 (result == 6))将具有类型为 WrappedResult[T] 的给定值,以便传递给 result 方法。

WrappedResult 是一个新类型,以确保我们不会在范围内获得不需要的给定值(在涉及上下文参数的所有情况下,这是一个好习惯)。

由于 WrappedResult 是一个不透明类型别名,因此其值不需要装箱,并且由于 ensuring 被添加为扩展方法,因此其参数也不需要装箱。因此,ensuring 的实现与人们可以手工编写的最佳代码的效率非常接近

val s =
  val result = List(1, 2, 3).sum
  assert(result == 6)
  result

参考

有关更多信息,请参阅 博客文章(其中使用了已过时的不同语法)。

更多详细信息