Как разработать функции высшего порядка - PullRequest
0 голосов
/ 30 октября 2018

Функции высшего порядка имеют параметр

  • тип функции или
  • тип функции с приемником.

Мы привыкли к filter и with из 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, используют it для доступа к элементу итерируемого, в то время как лямбды, переданные в with, используют 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 голосов
/ 30 октября 2018

Мое эмпирическое правило таково:

всякий раз, когда есть малейшая вероятность того, что мне понадобится назвать свой параметр лямбда, я использую (Type) -> Unit.

Если я уверен, что я не назову его (поэтому из контекста ясно, что все, над чем я работаю, это this) или я даже хочу запретить именование (строитель?), Тогда я использую 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 ->) он становится только уродливее и ничего не добавляет в контекст ...

0 голосов
/ 30 октября 2018

Вы просто не всегда хотите работать с приемниками. Например, если учесть, что ваша filter работала с элементами напрямую, вам нужно использовать в сравнении квалификатор this:

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

Это выглядит странно и неестественно. На что указывает this? Вы изменили область действия this, чтобы указать на приемник, и если вы хотите получить доступ к «внешнему» this, это будет выглядеть так:

this@SomeClass.c =="f"

Еще одним недостатком является то, что вы теряете возможность называть свой параметр. Подумайте, например, о вложенных лямбдах. Тогда ни it, ни this не подходит. Вы должны были бы дать пользовательские имена.

Вы всегда должны учитывать, действительно ли вы хотите переключиться на приёмник. Некоторые ситуации идеально подходят для использования, особенно DSL . Для обычных функций более высокого порядка вы просто не хотите иметь эту функцию.

Я думаю, что трудно сформулировать «правило» для этого, но для начала вы можете прочитать, что JetBrains рекомендует о том, как выбирать доступные функции области видимости (let, run, also apply with):

Вы вызываете методы для нескольких объектов в блоке или передаете экземпляр объекта контекста в качестве аргумента? Если вы, используйте одну из функций, которая позволяет вам получить доступ к объекту контекста, как он, а не это (также или пусть). Используйте также, если приемник вообще не используется в блоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...