代码之家  ›  专栏  ›  技术社区  ›  Frank Neblung

如何设计高阶函数

  •  3
  • Frank Neblung  · 技术社区  · 6 年前

    高阶函数的参数为

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

    我们习惯了 filter with 从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")
    }
    

    同时 滤波器 使用函数类型参数, 具有 对接收器使用函数类型参数。所以兰达斯传给 滤波器 使用 it 在lambdas传递给 具有 使用 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 回复  |  直到 6 年前
        1
  •  3
  •   Roland    6 年前

    我的经验法则如下:

    只要有一点点可能需要命名lambda参数,我就使用 (Type) -> Unit .

    如果我确定我不会命名它(因此从上下文可以清楚地看出,我所操作的一切都是 this )或者我甚至想禁止命名(builder?)然后我用 Type.() -> Unit .

    with , apply 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 或参数(例如 builder -> )它只是变得更丑,它并没有真正增加一些东西的上下文…

        2
  •  3
  •   Frank Neblung    6 年前

    你并不总是想和接受者一起工作。例如,考虑一下 filter 直接处理元素时,必须使用 this 那么比较中的限定符:

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

    看起来很奇怪很不自然。什么? 指向? 你改变了 指向接收器,如果你想访问“外部” ,它将如下所示:

    this@SomeClass.c =="f"
    

    另一个缺点是,您失去了命名参数的可能性。例如,考虑嵌套lambdas。既不 it 也不 那就合适了。你得给自己起个名字。

    你应该经常考虑是否真的想切换到接收器的范围。有些情况是完美的用例,特别是 DSLs . 对于通常的高阶函数,您只是不希望具有此功能。

    我认为很难制定一个“规则”,但是作为一个初学者,你可以阅读JeTeh。 recommends 如何在可用作用域函数之间进行选择( let , run , also , apply , with ):

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

    推荐文章