Lambda 作用域函数apply/with/run/allow/let:名称来自哪里?
有相当多的博客文章(如)介绍了标准库函数的用法Lambda 作用域函数apply/with/run/allow/let:名称来自哪里?,lambda,kotlin,higher-order-functions,scoping,Lambda,Kotlin,Higher Order Functions,Scoping,有相当多的博客文章(如)介绍了标准库函数的用法apply/with/run/也可以使用/let,这使得区分何时实际使用那些漂亮的函数变得更容易 几周来,官方文件甚至最终提供了关于该主题的指导方针: 然而,我认为通过函数名来记忆函数的个别用例是相当困难的。我的意思是,对我来说,它们似乎是可以互换的,比如说,let为什么不被称为run 有什么建议吗?我认为这些名字没有很好的表现力,这使得一开始很难看出它们之间的区别。这里是一个非官方的概述,说明这些名字是如何形成的 让 受到函数式编程世界的启发。据
apply
/with
/run
/也可以使用/let
,这使得区分何时实际使用那些漂亮的函数变得更容易
几周来,官方文件甚至最终提供了关于该主题的指导方针:
然而,我认为通过函数名来记忆函数的个别用例是相当困难的。我的意思是,对我来说,它们似乎是可以互换的,比如说,let
为什么不被称为run
有什么建议吗?我认为这些名字没有很好的表现力,这使得一开始很难看出它们之间的区别。这里是一个非官方的概述,说明这些名字是如何形成的
让
受到函数式编程世界的启发。据
“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
具有
该函数的灵感来源于和语言结构,这些语言来自或(可能还有许多其他)where等语言
with关键字是Delphi为引用提供的便利
复杂变量的元素,如记录或对象
myObject.colour := clRed;
myObject.size := 23.5;
myObject.name := 'Fred';
可以重写:
with myObject do
begin
colour := clRed;
size := 23.5;
name := 'Fred';
end;
与科特林相当的是
with(myObject) {
color = clRed
size = 23.5
name = "Fred"
}
应用
在里程碑阶段(M13)相对较晚的时候添加到stdlib。你们可以看到2015年的一个问题,用户要求使用这样一个函数,甚至建议使用后面的名称“apply”
在问题中,您可以看到有关命名的讨论。像build
和init
这样的备选方案被提出,但丹尼尔·沃多潘提出的名称apply
,最终获胜
apply
与with
类似,因为它可以用于初始化构造函数外部的对象。这就是为什么在我看来,apply
也可以被命名为with
。然而,由于stdlib中首先添加了with
,Kotlin开发人员决定不破坏现有代码,并以不同的名称添加了它
具有讽刺意味的是,Xtend语言提供了所谓的
也
添加到stdlib的时间甚至晚于apply
,即1.1版。同样,包含讨论内容。该函数基本上类似于apply
,不同的是它采用常规的lambda(T)->单元
,而不是扩展lambdaT.()->单元
拟议名称包括“applyIt”、“applyIt”、“on”、“tap”、“touch”、“peek”、“make”。但“也”赢了,因为它不会与任何关键字或其他stdlib函数发生冲突,而且它的用法(或多或少)读起来像英语句子
范例
val object = creater.createObject().also { it.initiliaze() }
读起来有点像
Creater,创建对象并初始化它
其他stdlib函数,其用法读起来有点像英语句子,包括,也被添加到1.1版中
跑
最后,函数实际上有两个签名。第一个fun run(block:()->R):R
只需取一个lambda并运行它。它主要用于将lambda表达式的结果指定给顶级属性
val logger = run {
val name = System.property("logger_name")
Logger.create(name)
}
第二个签名fun T.run(block:T.()->R):R
是一个扩展函数,它以扩展lambda为参数,出于对称性原因,似乎也被命名为“run”。它还“运行”一个lambda,但在扩展接收器的上下文中
val result = myObject.run {
intitialize()
computeResult()
}
我不知道命名的任何历史原因。在@kirillRakhman的答案中添加:
命名过程的一个主要部分是(仍然是)在主要用例中流畅的阅读体验
与
:
with(database) {
open()
send()
close()
}
应用
:
val v = View().apply {
width = 3.0
height = 4.0
register(this)
}
同时
:
db.users()
.filter { it.age > 18 }
.map { account }
.also { log(it) }
我知道它对不起作用,那就让好了。毕竟,它取自“那些可怕的FP语言”。但我经常认为这是一种让我们这样做吧代码>构造。如下图所示,您可以将代码读为,让我们打印它代码>:
account.map { it.owner }.sumBy {age}.let { print(it) }
为了理解所有这些作用域函数,我强烈建议阅读本文
这些博客的一些关键点:
LARA功能
在每个字母的第一个字母之后,你会得到首字母缩写“LARA”
代码比较
常见用例
- 对象-let()
- -也()
- -运行()
- 对于分配-应用()
与
with()
在功能上与run()
的扩展函数版本相同,因此它非常适合的用例。更多。这些都是非常情景化的功能。你可以在需要的时候用它们来记住它们。例如:我想知道以后如何继续?。使用nullables,我发现let
。我是当场学会的。与
或“还”的相同。一旦你知道它们的存在,当你知道有些东西但你不记得是什么东西时,你就会陷入这种情况。多次访问文档将确保您将来了解它们。至少我是这样做的这对我来说也不是什么问题。我总是知道其中一个可以在特定情况下使用。但问题是,函数名并不能真正说明它们真正的功能。好吧,你知道编程中最难的事情……不久前,当你回答一个以前和现在都被问到的问题时,我大惊小怪