Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

with语句

(With Statements)

据我所知,Koka 是第一个把尾随 Lambda 作为通用语法特性并与缩进布局深度整合的语言。它也是最早拥有点表示法的语言之一(这是独立开发的,但事实证明 D 语言具有类似的功能(称为 UFCS),它早于点表示法)。

另一个新颖的语法是 with 语句。由于可以轻松地将函数块(多行同层代码形成一个块,整个可被包起来采用为匿名函数)作为参数传递,它们通常会被嵌套使用。
先看一段代码:

fun twice(f)
  f()
  f()

fun test-twice()
  twice
    twice
      println("hi")

其中“hi“被打印四次(注意:这脱糖为

twice( fn(){ twice( fn(){ println("hi") }) })

)。使用 with 语句我们可以同缩进地写为:

pub fun test-with1()
  with twice
  with twice
  println("hi")

with 语句本质上将其后面的所有语句放入匿名函数中,并将该匿名函数作为最后一个参数传递。一般来说:

with f(e1,...,eN)
<body>
可被翻译为
f(e1,...,eN, fn(){ <body> })

此外,with 语句还可以绑定变量参数,如下所示:

with x <- f(e1,...,eN)
<body>
可被翻译为
f(e1,...,eN, fn(x){ <body> })

下面是一个使用 foreach 跨越函数体其余部分的示例:

pub fun test-with2() {
  with x <- list(1,10).foreach
  println(x)
}

其中函数体脱糖为

list(1,10).foreach( fn(x){ println(x) } )

这让人想起 Haskell do 表示法。以这种方式使用 with 语句一开始可能看起来有点奇怪,但在实践中非常方便 - 它有助于将 with 视为词法范围统摄当前块下方其余部分代码的闭包。

with finally

finally 函数将匿名函数采用为其第一个参数,离开作用域时回调这个函数,无论正常退出或通过“exception”(即当效应操作未恢复时)退出。with 与其配合很好:

fun test-finally()
  with finally{ println("exiting..") }
  println("entering..")
  throw("oops") + 42

脱糖为

finally(fn(){ println(...) }, fn(){ println("entering"); throw("oops") + 42 })

打印为

entering..
exiting..
uncaught exception: oops

这是 最小生成原则(min-gen principle) 的一个例子:许多语言对这种模式都有特殊的内置支持,比如 defer 语句,但在 Koka 中,这只是一种简朴的函数的语法糖的应用。

with‌ handler

with 语句与效应处理器(effect handler)结合使用特别有用。效应描述了一组抽象操作,其具体实现可以由处理器动态绑定。以下是用于发出消息的效应处理程序的示例:

操作(operation)就是你自定义的一种“效应原语“——它声明了“程序可能会做某件事“,但不规定这件事具体怎么做;具体怎么做由后面的 处理器(handler)​ 来提供。

// 生命一个抽象的操作: emit, 它如何发出消息由处理器动态定义。
effect fun emit(msg : string) : ()

// 发出一个标准的问候
fun hello() : emit ()
  emit("hello world!")

// 发出一个标准的问候到终端
pub fun hello-console1() : console ()
  with handler
    fun emit(msg) println(msg)
  hello()

在此示例中,with 表达式脱糖为

(handler{ fun emit(msg){ println(msg) } })( fn(){ hello() } )

通常,handler{ <ops> }表达式将一个函数块采用为其最后一个参数,因此可以直接与 with 一起使用。

此外,为了方便起见,对于仅定义一个操作的效应(比如 emit),我们可以省略 handler 关键字:

with val op = <expr>
with fun op(x){ <body> }
with ctl op(x){ <body> }
可被翻译为
with handler{ val op = <expr> }
with handler{ fun op(x){ <body> } }
with handler{ ctl op(x){ <body> } }

利用这种便利,我们可以将前面的示例更简洁地编写为:

pub fun hello-console2()
  with fun emit(msg) println(msg)
  hello()

直观地,我们可以将处理器with fun emit的作用视为给作用域内剩余内容动态绑定了一个对 emit 函数的定义