【问题标题】:How to design higher order functions如何设计高阶函数
【发布时间】:2018-10-30 13:45:09
【问题描述】:

高阶函数的参数是任意一个

  • 函数类型或
  • 带接收器的函数类型。

我们习惯了filterwith来自kotlin的stdlib:

@Test
fun `filter example`() {
    val filtered = listOf("foo", "bar").filter {
        it.startsWith("f")
    }

    assertThat(filtered).containsOnly("foo")
}

@Test
fun `with example`() {
    val actual = with(StringBuilder()) {
        append("foo")
        append("bar")
        toString()
    }

    assertThat(actual).isEqualTo("foobar")
}

filter 使用函数类型参数,with 使用带有接收器的函数类型参数。所以传递给filter 的lambdas 使用it 来访问iterable 的元素,而传递给with 的lambdas 使用this 来访问StringBuilder。

我的问题:当我声明自己的高阶函数时,是否有经验法则,使用哪种样式(it vs this)?


换句话说: 为什么过滤不是这样设计的?

inline fun <T> Iterable<T>.filter2(predicate: T.() -> Boolean): List<T> = filter { it.predicate() }

如果它是这样定义的,我们会这样使用它:

@Test
fun `filter2 function type with receiver`() {
    val filtered = listOf("foo", "bar").filter2 {
        // note: no use of it, but this
        startsWith("f")
    }

    assertThat(filtered).containsOnly("foo")
}

【问题讨论】:

  • 第一个是lamabda,第二个是带有接收器的lambda。

标签: lambda kotlin functional-programming


【解决方案1】:

我的经验法则如下:

只要我有可能需要命名我的 lambda 参数,我就会使用 (Type) -&gt; Unit

如果我确定我不会命名它(所以从上下文中可以清楚地看出我操作的所有东西都是this)或者我什至想禁止命名(builder?),那么我使用Type.() -&gt; Unit .

withapplyrun 都使用第二种方法……对我来说这是有道理的:

with(someString) {
  toUpperCase() // operates on someString... already sounds like: "with some string (do) to upper case"
}
someString.run(::println) // run println with someString; actually: someString.run { println(this) } // e.g.: print this [some string]  
// actually I find this a bad sample... I usually use run where the thing to be run is from the receiver... 
SomeComplexObject().apply {
  // apply being similar to a builder
  complex1 = 3 
  complex2 = 4
}.run {
  // fully constructed complex object
  complexOperationWithoutReturnValue() // this method is part of SomeComplexObject
} // discarding everything here...

这是我使用第二种方法的示例:

fun sendMail(from : String, to : String, msgBuilder : MessageBuilder.() -> Unit)

sendMail("from", "to") {
  subject("subject")
  body("body")
}

使用it 或参数(例如builder -&gt;)只会变得更丑陋,而且它并没有真正为上下文添加一些东西......

【讨论】:

    【解决方案2】:

    您并不总是想与接收器一起工作。例如,假设您的 filter 直接在元素上工作,那么您必须在比较中使用 this 限定符:

    val filtered = listOf("foo", "bar").filter2 {
        this == "f"
    }
    

    这看起来很奇怪和不自然。 this 指向什么? 您将this 的范围更改为指向接收者,如果您想访问“外部”this,它看起来像这样:

    this@SomeClass.c =="f"
    

    另一个缺点是您失去了命名参数的可能性。例如,考虑嵌套的 lambda。那时itthis 都不合适。您必须提供自定义名称。

    您应该始终考虑是否真的要切换到接收器的范围内。有些情况是完美的用例,尤其是DSLs。对于通常的高阶函数,你根本不想拥有这个特性。

    我认为很难为此制定“规则”,但作为初学者,您可以阅读 JetBrains recommends 关于如何在可用范围函数之间进行选择的内容 (let,run,also,@ 987654335@,with):

    您是在块中的多个对象上调用方法,还是将上下文对象的实例作为参数传递?如果是,请使用允许您访问上下文对象的函数之一,而不是 this(也或 let)。如果块中根本不使用接收器,也可以使用。

    【讨论】:

      猜你喜欢
      • 2019-07-28
      • 1970-01-01
      • 2016-05-07
      • 2013-12-22
      • 1970-01-01
      相关资源
      最近更新 更多