作用域函数apply/with/run/also/let:它们的名字从何而来?
Scope functions apply/with/run/also/let: Where do their names come from?
关于标准库函数apply
/with
/run
/also
的用法有很多博客文章(如this) /let
可用,可以更轻松地区分何时实际使用这些漂亮功能中的哪些。
几个星期以来,官方文档甚至终于提供了关于该主题的指南:https://kotlinlang.org/docs/reference/coding-conventions.html#using-scope-functions-applywithrunalsolet
尽管如此,我认为很难通过 函数名称 来记住函数的 个别用例 。我的意思是,对我来说它们似乎可以互换,例如为什么 let
不被称为 run
?
有什么建议吗?我认为名称不是很有表现力,因此一开始很难看出差异。
这是一个非官方的概述,关于这些名字似乎是如何形成的。
让
let
is inspired by the functional programming world. According to Wikipedia
a "let" expression associates a function definition with a restricted scope
在 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
的典型用法是将一些计算的结果绑定到没有 "polluting" 外部范围的范围。
creater.createObject().let {
if (it.isCorrect && it.shouldBeLogged) {
logger.log(it)
}
}
// `it` is out of scope here
与
with
function is inspired by the with
language construct from languages like Delphi or Visual Basic(可能还有许多其他人)
The with keyword is a convenience provided by Delphi for referencing
elements of a complex variable, such as a record or object.
myObject.colour := clRed;
myObject.size := 23.5;
myObject.name := 'Fred';
can be rewritten :
with myObject do
begin
colour := clRed;
size := 23.5;
name := 'Fred';
end;
等效的 Kotlin 是
with(myObject) {
color = clRed
size = 23.5
name = "Fred"
}
申请
apply
was added to the stdlib relatively late in the milestone phase (M13). You can see 2015 年的问题,其中用户要求正是这样的功能,甚至建议稍后使用的名称 "apply".
在问题https://youtrack.jetbrains.com/issue/KT-6903 and https://youtrack.jetbrains.com/issue/KT-6094中你可以看到关于命名的讨论。 build
和 init
等替代方案被提出,但 Daniil Vodopian 提出的名称 apply
最终获胜。
apply
与 with
类似,它可以用于在构造函数之外初始化对象。这就是为什么,在我看来,apply
也可以命名为 with
。然而,由于 with
首先被添加到 stdlib,Kotlin 开发人员决定不破坏现有代码并以不同的名称添加它。
具有讽刺意味的是,Xtend 语言提供了所谓的 with-operator =>
,它基本上与 apply
相同。
还有
also
was added to the stdlib even later than apply
, namely in version 1.1. Again, https://youtrack.jetbrains.com/issue/KT-6903 包含讨论。该函数基本上类似于 apply
,只是它采用常规 lambda (T) -> Unit
而不是扩展 lambda T.() -> Unit
.
提议的名称包括 "applyIt"、"applyLet"、"on"、"tap"、"touch"、"peek"、"make".但是 "also" 获胜,因为它不会与任何关键字或其他标准库函数冲突,而且它的用法(或多或少)读起来像英语句子。
例子
val object = creater.createObject().also { it.initiliaze() }
读起来有点像
Creater, create the object and also initialize it!
其他用法读起来有点像英文句子的 stdlib 函数包括 takeIf
and takeUnless
,它们也在 1.1 版中添加。
运行
最后,run
函数实际上有两个签名。第一个 fun <R> run(block: () -> R): R
简单地接受一个 lambda 和 运行s 它。它主要用于将 lambda 表达式的结果分配给顶级 属性
val logger = run {
val name = System.property("logger_name")
Logger.create(name)
}
第二个签名fun <T, R> T.run(block: T.() -> R): R
是一个扩展函数,它以扩展lambda作为参数,并且出于对称原因似乎也被命名为"run"。它也是 "runs" 一个 lambda 但在扩展接收器的上下文中
val result = myObject.run {
intitialize()
computeResult()
}
我不知道命名的任何历史原因。
添加到@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
配合使用效果不佳。毕竟是从"those scary FP languages"拿来的。但我经常将其视为一种 Let's do this!
结构。如下所示,您可以将代码阅读为 let's print it!
:
account.map { it.owner }.sumBy {age}.let { print(it) }
我强烈建议阅读此 blog 以了解所有这些作用域函数。
这些博客的一些关键:
- LARA 函数
在每个字母的第一个字母之后,你得到首字母缩写词“LARA”。
- 代码比较
常见用例
- Transforming 一个对象 - let()
- Create, pass, and evaluate - 也()
- Initialize and execute - 运行()
- Initialize an object 用于赋值 - apply()
和
with()
在功能上与 run()
的扩展函数版本相同,因此非常适合 Initialize and execute 的用例。更多信息.
作用域函数总结:
let: 用于检查空值,在多线程情况下也优于简单的空值检查
也:与'let'相同,但它不是return最后一行'let',而是'also'将 return 调用它的对象和 'not the last line!'
apply:修改对象的有用函数,如果你想改变对象的属性,它使用 'this' 而不是 'it' 作为我们在对象
的 class 内部工作
运行:相当于'apply',但它不会return它被调用的对象,而是return最后一行
with:与'run'相同,但签名不同。
关于标准库函数apply
/with
/run
/also
的用法有很多博客文章(如this) /let
可用,可以更轻松地区分何时实际使用这些漂亮功能中的哪些。
几个星期以来,官方文档甚至终于提供了关于该主题的指南:https://kotlinlang.org/docs/reference/coding-conventions.html#using-scope-functions-applywithrunalsolet
尽管如此,我认为很难通过 函数名称 来记住函数的 个别用例 。我的意思是,对我来说它们似乎可以互换,例如为什么 let
不被称为 run
?
有什么建议吗?我认为名称不是很有表现力,因此一开始很难看出差异。
这是一个非官方的概述,关于这些名字似乎是如何形成的。
让
let
is inspired by the functional programming world. According to Wikipedia
a "let" expression associates a function definition with a restricted scope
在 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
的典型用法是将一些计算的结果绑定到没有 "polluting" 外部范围的范围。
creater.createObject().let {
if (it.isCorrect && it.shouldBeLogged) {
logger.log(it)
}
}
// `it` is out of scope here
与
with
function is inspired by the with
language construct from languages like Delphi or Visual Basic(可能还有许多其他人)
The with keyword is a convenience provided by Delphi for referencing elements of a complex variable, such as a record or object.
myObject.colour := clRed; myObject.size := 23.5; myObject.name := 'Fred';
can be rewritten :
with myObject do begin colour := clRed; size := 23.5; name := 'Fred'; end;
等效的 Kotlin 是
with(myObject) {
color = clRed
size = 23.5
name = "Fred"
}
申请
apply
was added to the stdlib relatively late in the milestone phase (M13). You can see
在问题https://youtrack.jetbrains.com/issue/KT-6903 and https://youtrack.jetbrains.com/issue/KT-6094中你可以看到关于命名的讨论。 build
和 init
等替代方案被提出,但 Daniil Vodopian 提出的名称 apply
最终获胜。
apply
与 with
类似,它可以用于在构造函数之外初始化对象。这就是为什么,在我看来,apply
也可以命名为 with
。然而,由于 with
首先被添加到 stdlib,Kotlin 开发人员决定不破坏现有代码并以不同的名称添加它。
具有讽刺意味的是,Xtend 语言提供了所谓的 with-operator =>
,它基本上与 apply
相同。
还有
also
was added to the stdlib even later than apply
, namely in version 1.1. Again, https://youtrack.jetbrains.com/issue/KT-6903 包含讨论。该函数基本上类似于 apply
,只是它采用常规 lambda (T) -> Unit
而不是扩展 lambda T.() -> Unit
.
提议的名称包括 "applyIt"、"applyLet"、"on"、"tap"、"touch"、"peek"、"make".但是 "also" 获胜,因为它不会与任何关键字或其他标准库函数冲突,而且它的用法(或多或少)读起来像英语句子。
例子
val object = creater.createObject().also { it.initiliaze() }
读起来有点像
Creater, create the object and also initialize it!
其他用法读起来有点像英文句子的 stdlib 函数包括 takeIf
and takeUnless
,它们也在 1.1 版中添加。
运行
最后,run
函数实际上有两个签名。第一个 fun <R> run(block: () -> R): R
简单地接受一个 lambda 和 运行s 它。它主要用于将 lambda 表达式的结果分配给顶级 属性
val logger = run {
val name = System.property("logger_name")
Logger.create(name)
}
第二个签名fun <T, R> T.run(block: T.() -> R): R
是一个扩展函数,它以扩展lambda作为参数,并且出于对称原因似乎也被命名为"run"。它也是 "runs" 一个 lambda 但在扩展接收器的上下文中
val result = myObject.run {
intitialize()
computeResult()
}
我不知道命名的任何历史原因。
添加到@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
配合使用效果不佳。毕竟是从"those scary FP languages"拿来的。但我经常将其视为一种 Let's do this!
结构。如下所示,您可以将代码阅读为 let's print it!
:
account.map { it.owner }.sumBy {age}.let { print(it) }
我强烈建议阅读此 blog 以了解所有这些作用域函数。
这些博客的一些关键:
- LARA 函数
在每个字母的第一个字母之后,你得到首字母缩写词“LARA”。
- 代码比较
常见用例
- Transforming 一个对象 - let()
- Create, pass, and evaluate - 也()
- Initialize and execute - 运行()
- Initialize an object 用于赋值 - apply()
和
with()
在功能上与 run()
的扩展函数版本相同,因此非常适合 Initialize and execute 的用例。更多信息.
作用域函数总结:
let: 用于检查空值,在多线程情况下也优于简单的空值检查
也:与'let'相同,但它不是return最后一行'let',而是'also'将 return 调用它的对象和 'not the last line!'
apply:修改对象的有用函数,如果你想改变对象的属性,它使用 'this' 而不是 'it' 作为我们在对象
的 class 内部工作运行:相当于'apply',但它不会return它被调用的对象,而是return最后一行
with:与'run'相同,但签名不同。