На этом шаге мы рассмотрим функции этой категории.
Первая категория функций в функциональном программировании - это преобразователи. Функция-преобразователь работает с содержимым одной коллекции, выполняя обход элементов коллекции и изменяя каждый из них с помощью функции преобразования, заданной в аргументе. Функция-преобразователь возвращает копию измененной коллекции и передает управление следующей функции по цепочке.
Два часто используемых преобразователя - это map() и flatMap().
Функция-преобразователь map() перебирает элементы коллекции, для которой вызвана, и к каждому применяет функцию преобразования. В результате получается коллекция с таким же количеством элементов, что и первоначальная. Для примера введите следующий код в Kotlin REPL.
val animals = listOf("zebra", "giraffe", "elephant", "rat") val babies = animals .map{ animal -> "A baby $animal" } .map{ baby -> "$baby, with the cutest little tail ever!"} println(babies)
Рис.1. Переводит список взрослых животных в детенышей с хвостиками (REPL)
В функциональном программировании большое внимание уделяется возможности объединения функций для выполнения операций с последовательностью данных. В примере выше первый вызов map() принимает функцию преобразования
{ animal -> "A baby $animal" }
Следующая функция в цепочке, тоже map(), выполняет аналогичную последовательность действий, чтобы добавить милый хвостик каждому детенышу. В конце цепочки функций выводится итоговая коллекция с результатом применения двух операций map для каждого элемента:
A baby zebra, with the cutest little tail ever! A baby giraffe, with the cutest little tail ever! A baby elephant, with the cutest little tail ever! A baby rat, with the cutest little tail ever!
Ранее мы уже говорили, что функции-преобразователи возвращают измененную копию коллекции, для которой они вызваны. Они не изменяют первоначальную коллекцию. Вы убедитесь, что ничего не изменилось, если в REPL выведете содержимое animals.
print(animals) "zebra", "giraffe", "elephant", "rat"
Рис.2. Первоначальная коллекция не изменилась (REPL)
Как вы видите, первоначальная коллекция не изменилась. Преобразователь map() возвращает копию коллекции, применив указанную функцию преобразования к каждому ее элементу.
Такой подход позволяет отказаться от переменных, изменяющихся с течением времени. Более того, функциональный стиль программирования делает упор на неизменяемые копии данных, которые передаются от функции к функции по цепочке. Идея заключается в том, что изменяемые переменные делают программу более сложной в отладке. Также они увеличивают количество состояний, от которых зависит работа программы.
Ранее мы сказали, что map() возвращает такое же количество элементов, как в первоначальной коллекции. (Это верно не для всех функций-преобразователей, как вы увидите в следующих шагах.) Однако элементы в исходной и конечной коллекциях необязательно должны быть одного типа. Попробуйте ввести в REPL следующее.
val tenDollarWords = listOf("auspicious", "avuncular", "obviate") val tenDollarWordLengths = tenDollarWords.map { it.length } print(tenDollarWordLengths) [10, 9, 7] tenDollarWords.size res3: kotlin.Int = 3 tenDollarWordLengths.size res4: kotlin.Int = 3
Рис.3. До и после применения map(): одинаковое количество элементов, но разного типа (REPL)
Свойство size - это свойство, доступное для коллекций и определяющее количество элементов в списке или множестве или количество пар "ключ-значение" в ассоциативном массиве.
В этом примере map() получила три элемента и вернула три элемента. Изменился тип данных: коллекция tenDollarWords имеет тип List<String>, а список, сгенерированный функцией map(), - List<Int>.
Взгляните на сигнатуру функции map():
<T, R> Iterable<T>.map(transform: (T) -> R): ListR>
Функциональный стиль программирования доступен во многом благодаря тому, что Kotlin поддерживает функции высшего порядка. Как следует из ее сигнатуры, map() принимает функциональный тип. Вы не смогли бы передать функцию преобразования в map(), не имея возможности объявлять типы высшего порядка. Функция map() была бы совершенно бесполезной без своего параметра обобщенного типа.
Еще одна распространенная функция-преобразователь - это flatMap(). Функция flatMap() работает с коллекцией, содержащей коллекции, и возвращает объединенную "плоскую" коллекцию, содержащую все элементы исходных коллекций.
Для примера введите в Kotlin REPL следующее:
listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it } [1, 2, 3, 4, 5, 6]
Рис.4. Объединение двух списков в один (REPL)
В итоге получится новый список, содержащий элементы обоих подсписков. Обратите внимание, что количество элементов в исходной коллекции (2 - два подсписка) и итоговом списке (6) не равно.
На следующем шаге мы объединим flatMap() с другой категорией функций.
На следующем шаге мы рассмотрим фильтры.