【发布时间】:2023-03-11 03:20:02
【问题描述】:
This guideline point 建议在需要标识符时避免将 lambda 分配给变量。这有什么原因吗?是因为可读性吗?将绑定到变量的 lambda 作为参数传递给需要 Function 参数的函数对我来说似乎比传递声明的函数更自然(此外,声明函数自动完成会自动添加要删除的参数)。我很想听听你的意见。
【问题讨论】:
This guideline point 建议在需要标识符时避免将 lambda 分配给变量。这有什么原因吗?是因为可读性吗?将绑定到变量的 lambda 作为参数传递给需要 Function 参数的函数对我来说似乎比传递声明的函数更自然(此外,声明函数自动完成会自动添加要删除的参数)。我很想听听你的意见。
【问题讨论】:
我不知道该指南的确切理由,但有些原因是:
恒定性。顶级函数和static 方法是编译时常量。相比之下,Dart 目前不允许函数表达式(lambdas)成为常量。后果包括:
可优化性。当编译器看到一个调用点时,它就确切地知道将要调用什么。这允许它进行优化(例如可能内联函数调用)。
默认参数。因为 Dart 不允许函数表达式是常量,所以它们不能直接用作默认参数。例如:
int square(int x) => x * x;
final cube = (int x) => x * x * x;
void f([int Function(int) callback = square]) { // OK.
print(callback(42));
}
void g([int Function(int) callback = cube]) { // ERROR: Default value
// must be constant.
print(callback(42));
}
入口点。 main 等入口点以及用于生成 Isolates 的回调也必须是常量,因此它们只能声明为顶级函数或 static 方法,而不能声明为 lambda。
递归。声明的函数会立即绑定一个名称,但从函数表达式初始化变量必须首先计算表达式。如果函数表达式是自引用的(即递归的),则排序很重要。例如,这是不合法的:
final f = (int x) {
if (x > 0) {
print(x);
f(x - 1); // Error: Variable can't be referenced before it is declared.
}
};
而是需要单独声明变量:
late final void Function(int) f;
f = (int x) {
if (x > 0) {
print(x);
f(x - 1);
}
};
这对于本地函数来说是最尴尬的,而在其他地方是不切实际的。
返回类型。没有现有的语法可以直接指定 lambda 的返回类型,而是依赖于类型推断。因此,人们曾多次做过类似的事情:
() {
if (someCondition) {
return someValue;
}
}
他们忘记在所有代码路径中返回一个值,然后他们会困惑为什么他们的函数返回一个可为空的类型。相反,如果它被声明为具有显式返回类型的普通函数,则丢失的返回值将被捕获。您可以通过为 变量 提供显式类型来指定返回类型,但这让我想到了最后一点:
可读性。充其量使用 lambda 会稍微冗长一些,但几乎没有好处:
int square(int x) => x * x;
var square = (int x) => x * x;
如果你想声明一个返回类型并防止重新分配,那就更糟了:
final int Function(int) square = (x) => x * x;
此外,大多数函数都已命名,大多数现有函数都遵循该准则,并且如上所述,在某些情况下无法使用 lambda。因此存在很大的惯性。读者已经习惯了大部分时间看到函数声明,熟悉的代码本质上更具可读性。
请注意,关于常量和返回类型的观点并不能解释为什么语言是这样设计的。可以说像const square = (int x) => x * x; 这样的东西应该是合法的,并且可以发明一些新的语法来指定返回类型。 (我想反对const lambdas 的一个原因是它会使规范化变得更加困难。鉴于:
const foo = (int x) => x * x;
const bar = (int y) => y * y;
foo 应该是 identical 到 bar 吗?)实现这些东西并不是免费的,而且最终也没有这样做的动力。
【讨论】: