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

时间:2018-01-12 01:42:04

标签: lambda kotlin higher-order-functions scoping

有很多关于标准库函数apply / with / run / also / {的用法的博文(如this) {1}}可以让你更容易确定何时实际使用哪些漂亮的功能。

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

尽管如此,我认为用函数名称记住函数的个别用例非常困难。我的意思是,对我来说,它们似乎是可以互换的,为什么let不是let?{/ p>

有什么建议吗?我认为这些名字并不是非常富有表现力,因此最初很难看出这些差异。

3 个答案:

答案 0 :(得分:22)

以下是非官方概述这些名称的概况。

let的灵感来自功能编程世界。根据{{​​3}}

  

“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

Wikipedia功能的灵感来自withwith等语言的myObject.colour := clRed; myObject.size := 23.5; myObject.name := 'Fred'; 语言构造(可能还有许多其他语言)

  

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

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

可以改写:

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

等效的Kotlin将是

build

应用

在里程碑阶段(M13)相对较晚地将

Delphi添加到stdlib。您可以在2015年看到Visual Basic问题,其中用户要求提供此类功能,甚至建议稍后使用的名称为“apply”。

在问题applythis中,您可以看到有关命名的讨论。提出了initapply等替代方案,但由Daniil Vodopian提出的名称apply最终获胜。

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

具有讽刺意味的是,Xtend语言提供了所谓的https://youtrack.jetbrains.com/issue/KT-6903,它与apply基本相同。

https://youtrack.jetbrains.com/issue/KT-6094被添加到stdlib甚至晚于apply,即版本1.1。同样,with-operator =>包含讨论。该函数基本上类似于(T) -> Unit,除了它采用常规lambda T.() -> Unit而不是扩展lambda val object = creater.createObject().also { it.initiliaze() }

在提议的名称中有“applyIt”,“applyLet”,“on”,“tap”,“touch”,“peek”,“make”。但“也”赢了,因为它不会与任何关键字或其他stdlib函数碰撞,而且它的用法(或多或少)会像英语句子一样读取。

实施例

fun <R> run(block: () -> R): R

读取有点像

  

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

其他stdlib函数的用法读起来有点像英语句子,包括alsohttps://youtrack.jetbrains.com/issue/KT-6903,它们也在1.1版本中添加。

运行

最后,takeIf函数实际上有两个签名。第一个val logger = run { val name = System.property("logger_name") Logger.create(name) } 只需要一个lambda而运行。它主要用于将lambda表达式的结果赋值给顶级属性

fun <T, R> T.run(block: T.() -> R): R

第二个签名val result = myObject.run { intitialize() computeResult() } 是一个扩展函数,它将扩展lambda作为参数,并且出于对称原因似乎也被命名为“run”。它还“运行”lambda但在扩展接收器的上下文中

{{1}}

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

答案 1 :(得分:8)

我强烈建议您阅读此blog以了解所有这些范围功能。

这些博客的一些关键:

  1. LARA功能
  2. enter image description here

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

    1. 代码比较
    2. enter image description here

      1. 常见用例

      2. 。通过

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

答案 2 :(得分:7)

添加到@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) }