Функции как значения первого класса

В функциональном программировании функция — такое же значение, как число: её можно хранить, передавать и возвращать.

«Когда функции становятся значениями, программа превращается в конструктор, где детали — это поведения.»

Метод (через def) привязан к месту, где объявлен. А функция-значение — это самостоятельный объект, который можно положить в val. Записывается она с помощью стрелки =>.

val square = (x: Int) => x * x
val add = (a: Int, b: Int) => a + b

println(square(6))   // 36
println(add(2, 3))   // 5

Слева от => — параметры, справа — тело. Тип square — это Int => Int («функция из Int в Int»). Тип add(Int, Int) => Int.

Зачем это нужно

Раз функция — значение, её можно передать в другую функцию как аргумент. Это основа всей функциональной мощи Scala: вы описываете что делать, передавая поведение.

def applyTwice(f: Int => Int, x: Int): Int =
  f(f(x))

println(applyTwice(square, 2))  // square(square(2)) = 16
println(applyTwice(_ + 1, 10))  // 12

Подчёркивание _ — это краткая запись: _ + 1 означает (x) => x + 1.

Метод можно превратить в функцию

def triple(x: Int): Int = x * 3
val f = triple   // метод стал функцией-значением (eta-расширение)
println(f(7))    // 21

Та же идея на Python ▶

# Функции — значения первого класса и в Python
square = lambda x: x * x
add = lambda a, b: a + b

def apply_twice(f, x):
    return f(f(x))

print(square(6))                 # 36
print(apply_twice(square, 2))    # 16
print(apply_twice(lambda v: v + 1, 10))  # 12

# функцию можно положить в переменную и передать
fns = [square, add]
print(fns[0](5))                 # 25
функция-значение:
  (x: Int) => x * x
   \______/    \___/
   параметры   тело

  тип:  Int => Int
        вход  выход

Как работает под капотом (JVM)

У JVM нет понятия «функция-значение» — она знает только объекты. Поэтому Scala-функция (x: Int) => x * x компилируется в объект, реализующий интерфейс вроде Function1[Int, Int] с методом apply. Когда вы пишете square(6), на самом деле вызывается square.apply(6). В современных версиях для эффективности используются invokedynamic и лямбды JVM, так что накладные расходы минимальны.

Частые ошибки

  • Путать метод и функцию-значение. def — это метод, val f = (...) => ... — функция-значение. Передавать в higher-order функции можно оба.
  • Злоупотреблять _. Краткая запись удобна, но в сложных случаях явная лямбда читается лучше.
  • Забывать тип параметра в лямбде. Иногда Scala не может его вывести — тогда укажите явно.

Best practices

  • Передавайте поведение как функции — это делает код гибким и переиспользуемым.
  • Используйте _ для совсем коротких лямбд, явный синтаксис — для понятности.
  • Предпочитайте чистые функции без побочных эффектов.

Граница между данными и поведением стирается

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

Практически это открывает целый стиль программирования. Вместо того чтобы зашивать конкретное действие внутрь функции, вы принимаете действие параметром и применяете его. Так одна функция обслуживает множество сценариев. Краткая запись через подчёркивание делает простые лямбды почти невидимыми, не отвлекая от сути, а eta-расширение позволяет передавать обычные методы туда, где ждут функцию-значение. Освоив этот сдвиг, вы начнёте видеть в любой повторяющейся логике кандидата на вынос поведения наружу.

Важно держать в голове, что под капотом JVM такая функция — это объект с методом apply. Это объясняет, почему функции можно хранить в коллекциях, сравнивать по ссылке и передавать как любые другие объекты: для виртуальной машины они и есть объекты. Понимание этой механики снимает ощущение магии и помогает рассуждать о производительности, когда функций становится много.

Этот сдвиг в восприятии — момент, после которого функциональное программирование начинает раскрываться. Как только поведение становится таким же передаваемым значением, как число, открывается путь к функциям высшего порядка, замыканиям и всему богатству методов коллекций, которые вы изучите дальше. Поэтому стоит потратить время и по-настоящему прочувствовать идею «функция — это просто значение».

Итоги. Функция-значение пишется через =>, имеет тип вида A => B, и её можно хранить и передавать. Это фундамент функционального стиля. Дальше — функции высшего порядка.

Проверьте себя
1. Как записывается тип функции из Int в Int?
AInt -> Int
BInt => Int
CFunction(Int, Int)
DInt.Int
2. Что означает краткая запись _ + 1?
AПрибавить 1 к нулю
BФункцию (x) => x + 1
CУдалить элемент
DКомментарий