2.16. Lambda

Lambda 是无名函数,它们通过克隆、复制或引用来捕获本地上下文。 Lambda 比块慢,但在生命周期和捕获模式方面具有更大的灵活性 (见 Blocks).

lambda 类型可以使用类似函数的语法进行声明:

lambda_type ::= lambda { optional_lambda_type }
optional_lambda_type ::= < { optional_lambda_arguments } { : return_type } >
optional_lambda_arguments := ( lambda_argument_list )
lambda_argument_list := argument_name : type | lambda_argument_list ; argument_name : type

lambda < (arg1:int;arg2:float&):bool >

Lambda 可以是局部变量或全局变量,并且可以通过引用作为参数传递。 Lambda 可以移动,但不能复制或克隆:

def foo ( x : lambda < (arg1:int;arg2:float&):bool > ) {
    ...
    var y <- x
    ...
}

Lambda 可以通过 invoke 或类似调用的语法来调用:

def inv13 ( x : lambda < (arg1:int):int > ) {
    return invoke(x,13)
}

def inv14 ( x : lambda < (arg1:int):int > ) {
    return x(14)
}

Lambda 表达式通常通过 move 语法声明:

var CNT = 0
let counter <- @ (extra:int) : int {
    return CNT++ + extra
}
let t = invoke(counter,13)

lambda 和 block 声明之间有很多相似之处。 主要区别在于块是用 $ 符号指定的,而 lambda 是用 @ 符号指定的。 lambda 表达式也可以通过内联语法进行声明。 对于仅包含 return expression 的 lambda,也有类似的简化语法。 如果在泛型 或 函数中充分指定了 lambda,则将自动推断其类型(参阅 Blocks).

2.16.1. 捕获

与数据块不同,lambda 可以显式指定其捕获类型。有几种可用的捕获类型:

  • 通过复制
    • 通过移动

    • 按克隆

    • 按引用

通过引用捕获需要 unsafe。

默认情况下,将生成 capture by copy。如果 copy 不可用,则默认的 mobile 捕获需要 unsafe:

    var a1 <- [1,2]
    var a2 <- [1,2]
    var a3 <- [1,2]
    unsafe { // 通过引用捕获 a1 所需的 dO
            var lam <- @  capture(ref(a1),move(a2),clone(a3)) {
                    push(a1,1)
                    push(a2,1)
                    push(a3,1)
    }
            invoke(lam)
}

可以删除 lambda,这会导致对所有捕获的数据调用 Finalizer (参阅 Finalizers):

delete lam

Lambda 可以指定在默认终结器之前调用的自定义终结器:

var CNT = 0
var counter <- @ (extra:int) : int {
    return CNT++ + extra
} finally {
    print("CNT = {CNT}\n")
}
var x = invoke(counter,13)
delete counter                  // 这是调用 Finalizer 的时候

2.16.2. 迭代器

Lambda 是实现自定义迭代器的主要构建块 (参阅 Iterators).

Lambda 可以通过 eacheach_ref 函数转换为迭代器:

var count = 0
let lam <- @ (var a:int &) : bool {
    if ( count < 10 ) {
        a = count++
        return true
    } else {
        return false
    }
}
for ( x,tx in each(lam),range(0,10) ) {
    assert(x==tx)
}

要用作迭代器,lambda 必须

  • 具有一个参数,这是每个步骤迭代的结果

  • 具有布尔返回类型,其中 true 表示继续迭代,false 表示停止

制作 iterator 的一种更直接的方法是使用生成器 (参阅 Generators).

2.16.3. 实现细节

Lambda 是通过为捕获创建无名结构以及为 lambda 正文创建函数来实现的。

让我们看一个包含单个捕获变量的示例:

var CNT = 0
let counter <- @ (extra:int) : int {
    return CNT++ + extra
}

Daslang 将生成以下代码:

捕获结构:

struct _lambda_thismodule_7_8_1 {
    __lambda : function<(__this:_lambda_thismodule_7_8_1;extra:int const):int> = @@_lambda_thismodule_7_8_1`function
    __finalize : function<(__this:_lambda_thismodule_7_8_1? -const):void> = @@_lambda_thismodule_7_8_1`finalizer
    CNT : int
}

主体函数:

def _lambda_thismodule_7_8_1`function ( var __this:_lambda_thismodule_7_8_1; extra:int const ) : int {
    with ( __this ) {
        return CNT++ + extra
    }
}

终结器函数:

def _lambda_thismodule_7_8_1`finalizer ( var __this:_lambda_thismodule_7_8_1? explicit ) {
    delete *this
    delete __this
}

Lambda 创建被捕获结构的升序所取代:

let counter:lambda<(extra:int const):int> const <- new<lambda<(extra:int const):int>> (CNT = CNT)

C++ Lambda 类包含用于捕获数据的单个 void 指针:

struct Lambda {
    ...
    char *      capture;
    ...
};

通过引用传递 lambda 的原因在于,当调用 delete 时

  1. 为捕获数据调用 Finalizer

  2. 通过 NULL 替换捕获

缺少 copy 或 move 可确保没有多个指针指向捕获数据的单个实例。