范围函数适用于/ with/run/also/let:名称来自哪里?

s1m*_*nw1 12 lambda scoping higher-order-functions kotlin

有相当多的博客文章(像这样)的标准库的用途功能apply/ with/ run/ also/ let可用,使它更容易一点distingish时实际使用的这那些漂亮的功能.

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

不过,我认为这是相当困难的记忆功能的个人使用的情况下,函数名.我的意思是,对我来说,它们似乎是可以互换的,为什么不let被称为run例如?

有什么建议?我认为这些名字不是很有表现力,这使得一开始很难看出差异.

Kir*_*man 24

这是一个关于名称似乎如何形成的非正式概述.

let受功能编程世界的启发.根据维基百科

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

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

aaa = let y = 1+2
          z = 4+6
          in  y+z
Run Code Online (Sandbox Code Playgroud)

Kotlin中的等效代码(虽然过于复杂)

fun aaa() = (1+2).let { y -> 
              (4+6).let { z ->
                y + z
              } 
            }
Run Code Online (Sandbox Code Playgroud)

典型的用法let是将一些计算的结果绑定到一个范围而不"污染"外部范围.

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

// `it` is out of scope here
Run Code Online (Sandbox Code Playgroud)

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

with关键字是Delphi提供的一种便利,用于引用复杂变量的元素,例如记录或对象.

myObject.colour := clRed;
myObject.size   := 23.5;
myObject.name   := 'Fred';
Run Code Online (Sandbox Code Playgroud)

可以改写:

with myObject do
begin
  colour := clRed;
  size   := 23.5;
  name   := 'Fred';
end;
Run Code Online (Sandbox Code Playgroud)

相当于Kotlin

with(myObject) {
    color = clRed
    size = 23.5
    name = "Fred"
}
Run Code Online (Sandbox Code Playgroud)

应用

apply在里程碑阶段(M13)相对较晚地被添加到stdlib.你可以在2015年看到这个问题,用户要求提供这样的功能,甚至建议后来使用的名称"申请".

在问题https://youtrack.jetbrains.com/issue/KT-6903https://youtrack.jetbrains.com/issue/KT-6094中,您可以看到有关命名的讨论.替代喜欢buildinit提出但这个名字apply,由丹尼尔Vodopian建议,最终取得了胜利.

apply类似于with它可以用于初始化构造函数之外的对象.这就是为什么,在我看来,apply也可能被命名with.然而,正如with首先添加到stdlib中的那样,Kotlin开发人员决定不破坏现有代码并以不同的名称添加它.

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

also甚至晚apply于版本1.1 添加到stdlib 中.同样,https://youtrack.jetbrains.com/issue/KT-6903包含了讨论.该函数基本上就像apply它需要一个普通的lambda (T) -> Unit而不是一个扩展lambda T.() -> Unit.

提议的名称包括"applyIt","applyLet","on","tap","touch","peek","make".但"也"赢了,因为它不会与任何关键字或其他stdlib函数碰撞,并且它的用法(或多或少)读起来像英语句子.

val object = creater.createObject().also { it.initiliaze() }
Run Code Online (Sandbox Code Playgroud)

看起来有点像

创世,创建对象,并对其进行初始化!

其他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)
}
Run Code Online (Sandbox Code Playgroud)

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

val result = myObject.run {
    intitialize()
    computeResult()
}
Run Code Online (Sandbox Code Playgroud)

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


小智 9

我强烈建议您阅读此博客,以了解所有这些范围功能.

这些博客的一些关键:

  1. LARA功能

在此输入图像描述

在每个字母的第一个字母后面,您将获得首字母缩略词"LARA".

  1. 代码比较

在此输入图像描述

  1. 常见用例

with()在功能上与扩展函数版本相同run(),因此它非常适合初始化和执行的用例.更多信息.


vod*_*dan 7

添加到@kirillRakhman答案:

命名过程的主要部分是(仍然是)主要用例的流利阅读经验.

with:

with(database) {
    open()
    send()
    close()
}
Run Code Online (Sandbox Code Playgroud)

apply:

val v = View().apply {
    width = 3.0
    height = 4.0
    register(this)
}
Run Code Online (Sandbox Code Playgroud)

also:

db.users()
    .filter { it.age > 18 }
    .map { account }
    .also { log(it) }
Run Code Online (Sandbox Code Playgroud)

恕我直言,它并没有真正起作用let.毕竟,它取自"那些可怕的FP语言".但我经常认为它是一种Let's do this!结构.如下所示,您可以将代码读作let's print it!:

account.map { it.owner }.sumBy {age}.let { print(it) }
Run Code Online (Sandbox Code Playgroud)