【问题标题】:Why are lambdas bound to named variables to be avoided in Dart (According to Effective Dart)?为什么在 Dart 中要避免 lambdas 绑定到命名变量(根据有效 Dart)?
【发布时间】:2023-03-11 03:20:02
【问题描述】:

This guideline point 建议在需要标识符时避免将 lambda 分配给变量。这有什么原因吗?是因为可读性吗?将绑定到变量的 lambda 作为参数传递给需要 Function 参数的函数对我来说似乎比传递声明的函数更自然(此外,声明函数自动完成会自动添加要删除的参数)。我很想听听你的意见。

【问题讨论】:

    标签: dart lambda


    【解决方案1】:

    我不知道该指南的确切理由,但有些原因是:

    1. 恒定性。顶级函数和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。

    2. 递归。声明的函数会立即绑定一个名称,但从函数表达式初始化变量必须首先计算表达式。如果函数表达式是自引用的(即递归的),则排序很重要。例如,这是不合法的:

      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);
        }
      };
      

      这对于本地函数来说是最尴尬的,而在其他地方是不切实际的。

    3. 返回类型。没有现有的语法可以直接指定 lambda 的返回类型,而是依赖于类型推断。因此,人们曾多次做过类似的事情:

      () {
        if (someCondition) {
          return someValue;
        }
      }
      

      他们忘记在所有代码路径中返回一个值,然后他们会困惑为什么他们的函数返回一个可为空的类型。相反,如果它被声明为具有显式返回类型的普通函数,则丢失的返回值将被捕获。您可以通过为 变量 提供显式类型来指定返回类型,但这让我想到了最后一点:

    4. 可读性。充其量使用 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 应该是 identicalbar 吗?)实现这些东西并不是免费的,而且最终也没有这样做的动力。

    【讨论】:

    • 这实际上只是以下基本规则:使用最简单的方法。如果有两种方法可以做大致相同的事情,那么样式指南会告诉您更喜欢哪一种,这样每个人都可以做同样的事情,阅读代码的人不必想知道您为什么选择您所做的事情.与仅声明命名函数相比,创建变量并将其绑定到新函数值以通过名称引用函数过于复杂。如果你想确定返回类型,你需要在变量声明中放置一个函数类型。
    • 非常感谢您的彻底回答(以及您的评论@lrn)。你写的很有意义。我什至没有意识到 lambdas(或者至少是绑定到变量的 lambda 的纯 lambda 部分)不能指定返回类型。恒定性部分对我来说是全新的。通过考虑这两点,您的最后一点(可读性)非常有意义。你肯定回答了我的问题。
    • @JacobEmery 我添加了关于递归的另一点。另外,如果我的回答对你有帮助,请点赞并采纳。谢谢!
    • @jamesdlin 我试图给你投票,但我不能,因为我没有声望点。因此,我认为我也无法接受答案。顺便说一句,谢谢你关于递归的观点。
    猜你喜欢
    • 2017-04-28
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 2012-09-07
    • 2021-12-12
    • 2021-10-22
    • 2015-07-03
    相关资源
    最近更新 更多