Программирование на языке 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

Ответы на домашнее задание править