介绍

对于那些还没有使用过作用域函数的人,这里有一个解释。
我认为您可以从这篇文章中对范围函数有所了解。但是,由于它只是一种软性和主观的解释,它根本没有解释客观的严格定义。
作用域函数的详细解释可以参考《Kotlin 作用域函数使用总结》,整理的非常好。

动机

在《Kotlin 作用域函数使用总结》中,

作为个人意见,原则上我宁愿只使用 let 为 nullable。
首先,范围函数很难写出这样的文章并被原样阅读。

写了,但是我用了很多。我已经过去时了,因为我已经有几年没有在 kotlin 中开发了。那么你为什么决定写这篇文章呢?
最近几天我一直在用 Javascript 编写程序。那时,我想我想要一个作用域函数。除了 kotlin,我不知道任何提供范围函数的语言,而不仅仅是 Javascript。
我想在 Javascript 中创建自己的范围函数,所以在查看范围函数时,我查看了我之前提供的链接。而当我在写 kotlin 的时候,我觉得我亏欠了那个链接。
正如我所引用的,我遇到了一些关于使用范围函数的负面观点。四五年前我第一次读到它时,我不记得有没有提到那个观点,但我确实经常使用范围函数。当然,我使用了引用中提到的可为空的对策 let,但我也使用了许多其他的东西。
这是我个人的看法,但我觉得想用的人应该都能用。毕竟,我正在编写 javascript 并且想要一个范围函数。
我同意 let、also、run 和 apply 的使用可能是微妙而困难的。但这听起来并不像听起来那么难。
像你引用的解释很重要,但我认为如果你能理解程序员在使用范围函数时读写代码的感受会很有帮助。

评论

使用范围函数的程序

为了后面解释方便,下面给出了没有上色的源代码。描述范围函数很冗长。因此,将经过重构消除浪费的程序贴在最后。

import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale

class Person(var name: String, var bthdate: String) 
fun makeSentence(p: Person): String {
    val name = p.name
    val weekday = p.bthdate.let {
        val date = LocalDate.parse(it)
        val formmater : DateTimeFormatter = DateTimeFormatter.ofPattern("E", Locale.JAPANESE)
        date.format(formmater) 
    }
    return "%sは%s曜日に生まれました。".format(name, weekday)
}
fun main() {
    val taro = Person().also {
        it.name = "太郎"
        it.bthdate = "2001-01-01"
    }
    val jiro = Person().also {
        it.name = "次郎"
        it.bthdate = "2002-02-02"
    }
    val saburo = Person().also {
        it.name = "三郎"
        it.bthdate = "2003-03-03"
    }
    
    println(makeSentence(taro))
    println(makeSentence(jiro))
    println(makeSentence(saburo))
}

输出结果

太郎は月曜日に生まれました。
次郎は土曜日に生まれました。
三郎は月曜日に生まれました。

没有作用域函数的程序

想比较说明一下有没有使用的情况,所以下面写一个不使用作用域函数的版本。这也大胆地放了一些不上色的东西。

import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale

class Person(var name: String? = null, var bthdate: String? = null) 
fun makeSentence(p: Person): String {
    val name = p.name
    val date = LocalDate.parse(p.bthdate)
    val formmater : DateTimeFormatter = DateTimeFormatter.ofPattern("E", Locale.JAPANESE)
    val weekday = date.format(formmater) 
    return "%sは%s曜日に生まれました。".format(name, weekday)
}
fun main() {
    var taro = Person()
    taro.name = "太郎"
    taro.bthdate = "2001-01-01"
    var jiro = Person()
    jiro.name = "次郎"
    jiro.bthdate = "2002-02-02"
    var saburo = Person()
    saburo.name = "三郎"
    saburo.bthdate = "2003-03-03"
    
    println(makeSentence(taro))
    println(makeSentence(jiro))
    println(makeSentence(saburo))
}

使用also 函数的代码如下。

    val taro = Person().also {
        it.name = "太郎"
        it.bthdate = "2001-01-01"
    }
    val jiro = Person().also {
        it.name = "次郎"
        it.bthdate = "2002-02-02"
    }
    val saburo = Person().also {
        it.name = "三郎"
        it.bthdate = "2003-03-03"
    }

不使用的摘录也是

    var taro = Person()
    taro.name = "太郎"
    taro.bthdate = "2001-01-01"
    var jiro = Person()
    jiro.name = "次郎"
    jiro.bthdate = "2002-02-02"
    var saburo = Person()
    saburo.name = "三郎"
    saburo.bthdate = "2003-03-03"

是其一部分在我心里

  • 创建一个 Taro 实例。
  • 并设置名称。
  • 并设置您的生日。
  • 并创建一个 Jiro 的实例。
  • ...(省略)

以此类推,每一排都感觉均匀平整。

在我看来,使用也是这样的:

芋头 =人()。还{
it.name = "芋头"
it.bthdate = "2001-01-01"

}
瓦尔次郎 =人()。还{
it.name = "次郎"
it.bthdate = "2002-02-02"

}
沃尔三郎 =人()。还{
it.name = "三郎"
it.bthdate = "2003-03-03"

}

  • 创建一个 Taro 实例。
    - (顺便说一下,设置名字和生日。)
  • 创建 Jiro 的一个实例。
    - (顺便说一下,设置名字和生日。)
  • ...(省略)

我感受到了这条线的强弱。节奏出现在和弦中。
在变量中,也有 (人()) 可以读取。设置属性的部分也作为奖励可见。

这是使用 let 的摘录。

val weekday = p.bthdate.let {
    val date = LocalDate.parse(it)
    val formmater : DateTimeFormatter = DateTimeFormatter.ofPattern("E", Locale.JAPANESE)
    date.format(formmater) 
}

如果你也看一下,它看起来像这样:

val weekday = p.bthdate.{
val date = LocalDate.parse(it)
val formatter : DateTimeFormatter = DateTimeFormatter.ofPattern("E", Locale.JAPANESE)

  date.format(格式化程序)
}
let 的情况下,weekday 路上乱七八糟,不过暂时忽略了,最后传给 let 的函数的返回值,也就是最后一个date.format(格式化程序)输入后即可阅读。至于p.bthdate,我会记住的,但我猜它可能被乱用了。喜欢。比起那个来说date.format(格式化程序)更重要的是,比如。这就是来龙去脉。

也和让的总结

总之,也是剩下但让那个右边的最后一个{}分配给一个变量。
还有让看代码的时候暂时还可以,只要你看懂了。
其他代码行(那些在你最喜欢的程序中轻描淡写的行)将在必要时阅读,但乍一看它们会被轻描淡写。

好吧,计算机对每一行一视同仁,不加区别地对待它。但是,当我阅读和编写代码时,我会通过改变重点来处理它,例如权重、强度、重要性和非重要性。不然太辣了因为大脑的记忆很小。
$\LARGE{因为我是人类。 }$
(*在此处收集标题)

作用域函数在阅读代码时可用作标记优势和劣势的标记。至少对我来说。

申请并让

apply 是这个版本的also+it。 GUI 编程中的一个常见模式是设置大量属性,并且还使用

○○.also {
  it.○○ = ○○ 
  it.○○ = ○○
  it.○○ = ○○
  ...
}

将会在这种情况下,也用 apply 替换也会用 this 替换它,所以

○○.apply {
  this.○○ = ○○
  this.○○ = ○○
  this.○○ = ○○
  ...
}

变成。由于这在语法上是可选的,

○○.apply {
  ○○ = ○○
  ○○ = ○○
  ○○ = ○○
  ...
}

变成。

而 run 是这个版本的 let。但是,我个人不记得使用它和应用一样多。这。○○没有像应用一样显示出一致的模式,并且经常出现在不同的地方。因此,省略这一点使理解起来相当困难。在那种情况下,我觉得它是使用 let+it 明确说明的。用 run + this 明确指定 this 很好,但省略 this 的好处是 let 被压倒性地使用。

○○.run {
  ごにょごにょごにょ...
  this.○○ = = ごにょごにょ...
  ごにょごにょごにょ...
  this.○○ = ごにょごにょ...
  ...
}

和,

○○.run {
  ごにょごにょごにょ...
  ○○.○○(this, ....)
  ごにょごにょごにょ...
  (ごにょごにょ...ごにょごにょ).○○(this)
  ...
}

比如这个用到的地方就埋在很多代码里。前面的代码可以省略这个,但是省略它会让人难以理解。后面的代码不能省略,因为这是作为函数参数传递的。无论如何,如果代码是这样的,我会明确地将它与 let+it 一起使用,而不是 run+this。 apply也可以这样说,如果我省略了它,我就用also+it,如果我看不懂的话。

它可以是你喜欢的任何变量名

我会写它以防万一。如上面链接中所解释的,如果指定 lambda 表达式的参数,则可以将其设为易于理解的变量名,即使不是它。我们不会在本文中进一步讨论它。
在我的例子中,我主要使用它,但是当 lambda 表达式被嵌套并且很难理解它所指向的内容时,我使用了适当的变量名来代替它。首先,您应该尽可能避免嵌套。

对于那些难以区分 let、also、run 和 apply 的人

我感到困惑,因为我试图平等地记住这四个。我的建议是专注于让,也是第一。首先,扔掉它并只使用它。我记不得那么多了。$\large{因为我们是人类。 }$
然后您将了解 let 和 also first 之间的区别。一旦你习惯了,你应该用 this 替换它,并引入一个 run and apply 模式,省略 this,可以省略。
然而,正如我之前写的,在我的例子中,我使用了 apply 而几乎没有使用 run。

重构

重构后的程序毕竟没有使用作用域函数(笑)。这并不意味着永远不需要范围函数。我做的解释程序太简单了。因此,作用域功能的转向就消失了。

为了解释作用域功能,写作有意冗长。

import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale

fun main() {
    class Person(var name: String, var bthdate: LocalDate)
    listOf(
        Person("太郎", LocalDate.of(2001, 1, 1)),
        Person("次郎", LocalDate.of(2002, 2, 2)),
        Person("三郎", LocalDate.of(2003, 3, 3)),
    ).map {
    	val weekday = it.bthdate.format(DateTimeFormatter.ofPattern("E", Locale.JAPANESE)) 
        "%sは%s曜日に生まれました。".format(it.name, weekday)
    }.forEach(::println)
}

虽然没有使用scope函数,但是map函数的写法和let函数很相似。

// letに近い書き味
.map {
  ...
  ...it...
  ...
}

出于这个原因,我认为范围函数对于以类似流的方式编写的程序员来说感觉很自然。

在最后

写完这篇

我发现一个页面叫
大部分同意,但申请除外。但是,与其教条地禁止它,我更喜欢这种立场,即如果你想使用它,你就可以使用它。只要不使用奇怪的方式,我个人认为是可以的。
好吧,如果它是一个工作计划,我想你不能这么说。我认为这也取决于团队。

还有几件事我想提一下。

首先,我同意你的以下观点:

hoge?.let {
    ... it.○○ ...
}

不这样使用返回值感觉不舒服,所以不能使用。在这种情况下,老实说

if(hoge != null) {
    ... hoge.○○ ...
}

或者,为集合方法创建一个等效于 forEach 的扩展函数并在其中执行它。关于自制扩展功能,可能会在另一篇文章中介绍。

此外,它说“我在 Twitter 上被告知”

foo?.let { it.getInt() } ?: 0

是关于在 foo != null && it.getInt() == null 的情况下,有人认为不应使用 Elvis 运算符和 let 的组合,因为 foo?.let { it.getInt() } ?: 0 被评估为 0
关于这一点,是“我不知道你在说什么(Tomizawa)”。我宁愿积极使用它。

然而,在这个例子中,

foo?.getInt() ?: 0 

只是这么说,我不会费心使用 let 。

val bar = foo?.getInt() ?: 0 

这对我来说是一个反复出现的模式。怎么了?
100步,

val bar = foo?.getInt() 
// ないし
val bar = foo?.let { it.getInt() } 

所以,在foo == null || foo.getInt() == null 的情况下,如果你想让bar 成为null,你会明白的。

再次,它是在推特上写的

foo?.let { it.getInt() } ?: 0

回到在foo != null && it.getInt() == null 的情况下,如果foo?.let { it.getInt() } ?: 0 不能被评估为0,在foo == null 的情况下,评估为0,在foo.getInt() == null 的情况下,评估为null。你的意思是?
我个人不喜欢这样的节目。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308623245.html

相关文章: