【问题标题】:Scope functions apply/with/run/also/let: Where do their names come from?作用域函数 apply/with/run/also/let:它们的名字从何而来?
【发布时间】:2018-01-12 01:42:03
【问题描述】:

有很多关于标准库函数 apply/with/run/also/let 的用法的博客文章(如 this)可以更轻松地区分何时实际使用这些漂亮功能中的哪一个。

几周以来,官方文档甚至最终提供了关于该主题的指南:https://kotlinlang.org/docs/reference/coding-conventions.html#using-scope-functions-applywithrunalsolet

不过,我认为通过函数名称记住函数的各个用例是相当困难的。我的意思是,对我来说,它们似乎是可以互换的,例如,为什么 let 不称为 run

有什么建议吗?我认为这些名字不是很有表现力,所以一开始很难看出它们的区别。

【问题讨论】:

  • 这些是非常情境化的功能。您将在需要时通过使用它们来记住它们。例如:我想知道之后如何继续?使用 nullables,我找到了let。当场就学会了。与with 或“也”相同。一旦你知道它们存在,当你知道有什么但你不记得是什么时,你就会进入这些案例。多次访问文档将确保您将来了解它们。至少我是这样的
  • 这对我来说也不是问题。我总是知道其中一种可以在特定情况下使用。但问题是,函数名称并不能真正说明它们的实际作用。
  • 嗯,你知道编程中最难的事情......
  • 前段时间,当你回答了一个之前被问过的问题,后来被重复关闭时,我大惊小怪。我曾经过于热衷于保持这个网站的清洁以及它附带的一切,这导致我对你表现得不太好。从那以后,我学会了放下这种过度的热情,我为我的行为方式道歉。您在这里做得很好,非常感谢您的贡献,继续努力。

标签: lambda kotlin higher-order-functions scoping


【解决方案1】:

这里是一个非官方的概述这些名字的由来。

let 受到函数式编程世界的启发。根据Wikipedia

“let”表达式将函数定义与受限范围相关联

在像 Haskell 这样的 FP 语言中,您可以使用 let 将值绑定到像这样的受限范围内的变量

aaa = let y = 1+2
          z = 4+6
          in  y+z

Kotlin 中的等效代码(尽管过于复杂)是

fun aaa() = (1+2).let { y -> 
              (4+6).let { z ->
                y + z
              } 
            }

let 的典型用法是将某些计算的结果绑定到一个范围,而不会“污染”外部范围。

creater.createObject().let {
    if (it.isCorrect && it.shouldBeLogged) {
        logger.log(it)
    }
}

// `it` is out of scope here

with 函数的灵感来自于 DelphiVisual Basic(可能还有许多其他语言)等语言的 with 语言结构

with关键字是Delphi提供的方便引用 复杂变量的元素,例如记录或对象。

myObject.colour := clRed;
myObject.size   := 23.5;
myObject.name   := 'Fred';

可以改写:

with myObject do
begin
  colour := clRed;
  size   := 23.5;
  name   := 'Fred';
end;

等效的 Kotlin 将是

with(myObject) {
    color = clRed
    size = 23.5
    name = "Fred"
}

申请

apply 在里程碑阶段 (M13) 相对较晚的时候被添加到标准库中。您可以看到 2015 年的 this 问题,用户要求正是这样的功能,甚至建议后来使用的名称“应用”。

https://youtrack.jetbrains.com/issue/KT-6903https://youtrack.jetbrains.com/issue/KT-6094 问题中,您可以看到有关命名的讨论。有人提出了像 buildinit 这样的替代方案,但由 Daniil Vodopian 提出的名称 apply 最终获胜。

applywith 相似之处在于它可以用于在构造函数之外初始化对象。这就是为什么,在我看来,apply 还不如命名为with。然而,由于with 首先被添加到标准库中,Kotlin 开发人员决定不破坏现有代码并以不同的名称添加它。

具有讽刺意味的是,Xtend 语言提供了所谓的with-operator =>,它与apply 基本相同。

还有

also 被添加到 stdlib 甚至比apply 更晚,即在版本 1.1 中。同样,https://youtrack.jetbrains.com/issue/KT-6903 包含讨论。该函数与apply 基本相似,不同之处在于它采用常规 lambda (T) -> Unit 而不是扩展 lambda T.() -> Unit

建议的名称包括“applyIt”、“applyLet”、“on”、“tap”、“touch”、“peek”、“make”。但是“also”赢了,因为它不会与任何关键字或其他 stdlib 函数发生冲突,而且它的用法(或多或少)读起来像英文句子。

例子

val object = creater.createObject().also { it.initiliaze() }

有点像

创建者,创建对象并初始化它!

其他用法读起来有点像英文句子的stdlib函数包括takeIftakeUnless,它们也在1.1版中添加。

运行

最后,run 函数实际上有两个签名。第一个 fun <R> run(block: () -> R): R 只需要一个 lambda 并运行它。它主要用于将 lambda 表达式的结果分配给顶级属性

val logger = run {
    val name = System.property("logger_name")
    Logger.create(name)
}

第二个签名fun <T, R> T.run(block: T.() -> R): R 是一个扩展函数,它以扩展 lambda 作为参数,并且出于对称原因似乎也被命名为“run”。它还“运行”一个 lambda,但在扩展接收器的上下文中

val result = myObject.run {
    intitialize()
    computeResult()
}

我不知道命名的任何历史原因。

【讨论】:

  • 谢谢!对我来说,with 确实是有道理的,因为接收者作为第一个参数传递,我们用 with 做一些事情,因此我不希望 apply 被命名为 @987654373 @
【解决方案2】:

我强烈建议阅读blog 以了解所有这些作用域函数。

这些博客的一些关键:

  1. LARA 函数

在每个字母的第一个字母之后,您会得到首字母缩略词“LARA”。

  1. 代码比较

  1. 常见用例

with() 在功能上与run() 的扩展功能版本相同,因此非常适合Initialize and execute 的用例。更多information

【讨论】:

    【解决方案3】:

    添加到@kirillRakhman 答案:

    命名过程的一个主要部分是(仍然是)在主要用例中的流利阅读体验。

    with:

    with(database) {
        open()
        send()
        close()
    }
    

    apply:

    val v = View().apply {
        width = 3.0
        height = 4.0
        register(this)
    }
    

    also:

    db.users()
        .filter { it.age > 18 }
        .map { account }
        .also { log(it) }
    

    恕我直言,它不适用于let。毕竟,它取自“那些可怕的 FP 语言”。但我经常认为它是一种Let's do this! 构造。如下所示,您可以将代码阅读为let's print it!:

    account.map { it.owner }.sumBy {age}.let { print(it) }
    

    【讨论】:

    • 我记得我们最近在 slack 上讨论过 let ;-)
    • @s1m0nw1 我越讨论let,我对它的命名就越不满意:P
    【解决方案4】:

    作用域功能总结:

    let:用于检查空值,在多线程情况下也优于简单的空值检查

    also:与 'let' 相同,但它不会将最后一行作为 'let' 返回,而是 'also' 将返回它被调用的对象,而不是最后一行! '

    apply:修改对象的有用函数,如果你想改变对象的属性,它使用'this'而不是'it',因为我们在对象的类中工作

    run:相当于'apply',但它不会返回它被调用的对象,而是返回最后一行

    with:与“运行”相同,但签名不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-13
      • 1970-01-01
      相关资源
      最近更新 更多