如何设计高阶函数

时间:2018-10-30 13:45:10

标签: lambda kotlin functional-programming

高阶函数的参数为​​

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

我们习惯于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的lambda使用it访问iterable的元素,而传递给with的lambda使用this访问StringBuilder。

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


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

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")
}

2 个答案:

答案 0 :(得分:3)

您根本不总是希望与接收者一起工作。例如,考虑您的filter直接在元素上工作,则必须在比较中使用this限定词,然后:

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

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

this@SomeClass.c =="f"

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

您应该始终考虑是否真的要切换到接收器的示波器。有些情况是完美的用例,尤其是DSLs。对于通常的高阶函数,您根本不想拥有此功能。

我认为很难为此制定一个“规则”,但是作为一个入门者,您可以阅读关于如何在可用范围函数(letrun之间进行选择的JetBrains recommendsalsoapplywith):

  

您是在块中的多个对象上调用方法,还是将上下文对象的实例作为参数传递?如果是这样,请使用允许您按其访问上下文对象的功能之一,而不是此(也可以或让)。如果该块中根本没有使用接收器,也可以使用。

答案 1 :(得分:3)

我的经验法则如下:

只要有很小的可能需要命名我的lambda参数,我就使用(Type) -> Unit

如果我确定不会命名(因此从上下文中可以明显看出,我操作的所有内容都是this),或者我甚至想禁止命名(生成器?),那么我可以使用{ {1}}。

Type.() -> Unitwithapply都使用第二种方法……对我来说,这很有意义:

run

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

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)只会变得更丑陋,并且不会真正为上下文添加任何内容...