Программирование на языке Scala/Функции высшего порядка
Фу́нкция вы́сшего поря́дка (Higher-order functions) — функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата.
Назначение функций высокого порядка - это сокращение объема кода и увеличение универсальности функции за счет разделения функции на фиксированную часть и изменяемую часть. Фиксированная часть помещается в самой функции, а изменяемая часть передается функции как параметр.
Функции высокого порядка позволяют реализовывать паттерн "функции обратного вызова" (callback), где одна функция вызывает другую функцию после выполнения определенных действий. Это используется, например, для обработки событий в пользовательском интерфейсе.
Функции высокого порядка позволяют работать с абстракциями данных, такими как списки, множества и деревья, без необходимости знать их внутреннее представление.
Функции высокого порядка позволяют генерировать другие функции в процессе выполнения программы, в зависимости от получаемых данных. Это позволяет создавать универсальные и очень гибкие алгоритмы.
Так как иногда понимание и чтение функций высшего порядка бывает сложным для человека, стоит быть лаконичным при написании таких функций.
Пример функции высшего порядка и её использование:
val fun2: (String, Int, (Int => String)) => String =
(str: String, int: Int, fun: Int => String) => str + fun(int)
fun2("1", 2, s => s.toString)
где первый и второй параметр (str, int) это параметры значения, а третий параметр (fun) - это функциональный параметр. Функциональный параметр вызывается в теле функции с указанием аргумента.
Пример функции высшего порядка с функциональным выходным значением:
// Функция - генератор функций
val algorithmGen: Boolean => (Int => String) =
(isFirstAlgorithm: Boolean) =>
if (isFirstAlgorithm)
(int2: Int) => (int2 * 2).toHexString
else
(int2: Int) => (int2 * 3).toHexString
// Используем функцию algorithmGen для создания функций firstAlgorithm и secondAlgorithm
val firstAlgorithm: Int => String = algorithmGen(true)
val secondAlgorithm: Int => String = algorithmGen(false)
firstAlgorithm(2) // Результат 4
firstAlgorithm(3) // Результат 6
secondAlgorithm(2) // Результат 6
secondAlgorithm(3) // Результат 9
// Создание функции потребителя функции-генератора
val fun6: (Int, Int => String) => String =
(int, fun) => s"Это шестнадцатеричное число: ${fun(int)}"
fun6(4, algorithmGen(false))
Полиморфизм
правитьИспользование функций высших порядков можно рассматривать как одну из форм полиморфизма в программировании.
Полиморфизм означает способность объектов иметь несколько форм или типов. Он позволяет использовать один и тот же код для разных типов данных или объектов. Функции высших порядков позволяют работать с другими функциями как с данными. Они принимают одну или несколько функций в качестве аргументов или возвращают функции в качестве результата.
Использование функций высших порядков позволяет создавать более гибкие и абстрактные решения, позволяющие применить одну и ту же функциональность к разным типам данных или объектам. Это снижает повторяемость кода и упрощает его поддержку и расширение.
Некоторые методы (аналог функций) высшего порядка у типа String
правитьМетод map
правитьМетод map преобразует коллекцию символов в строке abc в коллекцию символов (строку) ABC. Метод map берет каждый элемент в строке, применяет к нему функцию up. Из полученных элементов формирует новую строку. Количество элементов в строке не меняется.
//Анонимная функция
val up: Char => Char = (c: Char) => c.toUpper
//Функция высшего порядка
val result = "abc".map(up)
Результат: ABC Другой пример:
//Анонимная функция
val incChar: Char => Char = (c: Char) => {
val charInt = c.toInt
val nextCharInt = charInt + 1
nextCharInt.toChar
}
//Функция высшего порядка
"abc".map(incChar)
Результат: bcd
Метод foreach
правитьМетод foreach аналогичен методу map за исключением формирования итогового значения. Метод предназначен для выполнения так называемых побочных эффектов таких как, например println() и возвращает тип Unit (пустое множество).
val santaGo: Char => Unit =
(c: Char) => println(s"$c ho-ho")
val go = "abc".foreach(santaGo)
Результат:
a ho-ho
b ho-ho
c ho-ho
Метод exists
правитьМетод exists возвращает true, если хотя бы один элемент в строке удовлетворяет проверке функции check.
val check: Char => Boolean =
(c: Char) => c.isUpper
"abC".exists(check)
Результат:
true
Метод count
правитьМетод count возвращает количество элементов, отобранных функцией check.
val check: Char => Boolean =
(c: Char) => c.isUpper
"abC".count(check)
Результат:
1
Метод flatMap
правитьМетод flatMap возвращает строку символов, которая создается из строк сгенерированных функцией.
val charRepeat: Char => String =
(c: Char) => c.toString.repeat(3)
"123abc".flatMap(charRepeat)
Результат:
111222333aaabbbccc
Метод forall
правитьМетод forall проверяет каждый элемент на соответствие указанной в функции. Все элементы должны соответствовать.
val checkABC: Char => Boolean =
(c: Char) => c.isLetter
"abc".forall(checkABC)
Результат:
true
Метод takeWhile
правитьМетод takeWhile забирает элементы из строки, пока элементы удовлетворяют условию функции.
val check: Char => Boolean = (c: Char) => c.isUpper "ABCd".takeWhile(check)
Результат:
ABC
Метод dropWhile
правитьМетод dropWhile удаляет элементы из строки, пока элементы удовлетворяют условию функции.
val check: Char => Boolean = (c: Char) => c.isUpper "ABCd".dropWhile(check)
Результат:
d
Сокращенные формы использования анонимной функции:
"ABCdd".dropWhile(c => c.isUpper) "ABCdd".dropWhile(_.isUpper)
Результат:
d
Метод foldLeft
правитьМетод foldLeft исполняет функцию для каждого элемента коллекции (символа). Метод позволяет работать с текущем элементом и накопленным значением в аккумуляторе. У метода два списка параметров, каждый список это круглые скобки. Первый список состоит из одного параметра, задающий начальное значение аккумулятора. Второй список также состоит из одного параметра, в котором указывается анонимная функция. Результат функции попадает в новое значение аккумулятора.
val summator: (Int, Char) => Int = (acc: Int, el: Char) => acc + el.toInt "123abc".foldLeft(0)(summator)
В данном примере мы посчитали сумму кодов символов всей строки. Результат:
444
Метод filter
правитьМетод filter фильтрует элементы удовлетворяющие функции.
val checkNumber: Char => Boolean = (c: Char) => c.isDigit "123a".filter(checkNumber)
Результат:
123
Метод filterNot
правитьМетод filterNot фильтрует элементы не удовлетворяющие функции.
val checkNumber: Char => Boolean = (c: Char) => c.isDigit "123a".filterNot(checkNumber)
Результат:
a
Метод indexWhere
правитьМетод indexWhere возвращает индекс первого попавшегося символа, удовлетворяющего функции.
val checkNumber: Char => Boolean =
(c: Char) => c.isDigit
"123abc".indexWhere(checkNumber)
Результат:
0
Более короткий пример:
"123abc".indexWhere(_.isLetter)
Результат:
0
Метод reduce
правитьМетод reduce сворачивает строку в одно значение с помощью функции. Не может работать с пустой строкой.
val summatorChar: (Char, Char) => Char =
(el: Char, next: Char) =>
el.max(next)
"abcz".reduce(summatorChar)
Результат:
z
Метод scanLeft
правитьМетод scanLeft преобразует элементы строки функцией, которая имеет текущее и предыдущие значение строки.
val summatorChar: (Char, Char) => Char =
(acc: Char, el: Char) =>
acc.max(el)
"abc".scanLeft('y')(summatorChar)
Результат:
Vector(y, y, y, y, z)
Метод segmentLength
правитьМетод segmentLength возвращает размер первой секции, удовлетворяющий функции.
val checkABC: Char => Boolean =
(c: Char) => c.isLetter
"abc1abcd".segmentLength(checkABC)
Результат:
4
Метод distinctBy
правитьМетод distinctBy преобразует элементы коллекции, а затем убирает одинаковые элементы.
val charUpper: Char => Char =
(c: Char) => c.toUpper
"abcABC".distinctBy(charUpper)
Результат:
abc
Домашнее задание
править1. В учебном проекте создать файл HigherOrderFunctions.sc (Scala-Worksheet)
2. Реализовать аналог метода map Использовать foldLeft.
Заготовка реализации:
"abc".map(char => (char.toInt + 1).toChar)
val mapFun: (String, Char => Char) => String =
??? // Реализация тут
mapFun("abc", char => (char.toInt + 1).toChar)
3. Реализовать аналог метода flatMap Использовать foldLeft.
Заготовка реализации:
"abc".flatMap(char => char.toString.repeat(3))
val flatMapFun: (String, Char => String) => String =
??? // Реализация тут
flatMapFun("abc", char => char.toString.repeat(3))
4. Реализовать аналог метода foreach Использовать foldLeft.
Заготовка реализации:
"abc".foreach(char => print(char))
val foreachFun: (String, Char => Unit) => Unit =
??? // Реализация тут
foreachFun("abc", char => print(char))
foreachFun("abc", print(_))
foreachFun("abc", print)
5. Реализовать аналог метода exists Использовать foldLeft.
Заготовка реализации:
"abc".exists(char => char.isDigit)
"a1c".exists(char => char.isDigit)
val existsFun: (String, Char => Boolean) => Boolean =
??? // Реализация тут
existsFun("abc", char => char.isDigit)
existsFun("a1c", char => char.isDigit)
6. Реализовать аналог метода filterNot Использовать foldLeft.
Заготовка реализации:
"abc".filterNot(char => char.isSpaceChar)
"abc d".filterNot(char => char.isSpaceChar)
"".filterNot(char => char.isSpaceChar)
val filterNotFun: (String, Char => Boolean) => String =
??? // Реализация тут
filterNotFun("abc", char => char.isSpaceChar)
filterNotFun("abc d", char => char.isSpaceChar)
filterNotFun("", char => char.isSpaceChar)
7. Реализовать аналог метода distinctBy Использовать foldLeft.
Заготовка реализации:
"abcA".distinctBy(_.toLower)
val distinctByFun: (String, Char => Char) => String =
??? // Реализация тут
distinctByFun("abcA", _.toLower)
8. Реализовать аналог метода forall Использовать foldLeft.
Заготовка реализации:
"abc".forall(char => char.isLetter)
"".forall(char => char.isLetter)
val forallFun: (String, Char => Boolean) => Boolean =
??? // Реализация тут
forallFun("a1c", char => char.isLetter)
forallFun("abc", char => char.isLetter)
forallFun("", char => char.isLetter)
9. Реализовать аналог метода count Использовать foldLeft.
Заготовка реализации:
"a1c".count(char => char.isDigit)
val countFun: (String, Char => Boolean) => Int =
??? // Реализация тут
countFun("abc", char => char.isDigit)
countFun("a1c", char => char.isDigit)
10. Реализовать аналог метода takeWhile Использовать foldLeft.
Заготовка реализации:
"abc123".takeWhile(char => char.isDigit)
"1abc2".takeWhile(char => char.isDigit)
"".takeWhile(char => char.isDigit)
val takeWhileFun: (String, Char => Boolean) => String =
??? // Реализация тут
takeWhileFun("abc123", char => char.isDigit)
takeWhileFun("1abc2", char => char.isDigit)
takeWhileFun("", char => char.isDigit)
11. Реализовать аналог метода dropWhile Использовать foldLeft.
Заготовка реализации:
"abc123".dropWhile(char => char.isDigit)
"1abc2".dropWhile(char => char.isDigit)
"".dropWhile(char => char.isDigit)
val dropWhileFun: (String, Char => Boolean) => String =
??? // Реализация тут
dropWhileFun("abc123", char => char.isDigit)
dropWhileFun("1abc2", char => char.isDigit)
dropWhileFun("", char => char.isDigit)
12. Создать функцию генератор, которая создает функцию для метода filter строки. Если длина строки больше 3, то надо фильтровать числовые символы, если нет то буквенные. Заготовка реализации:
val filterGen =
??? // Реализация тут
val str = "abc"
str.filter(filterGen(str)) // Результат abc
val str = "abc1"
str.filter(filterGen(str)) // Результат 1