Параметры по умолчанию и именованные аргументы
Хороший API не заставляет помнить порядок аргументов — Scala даёт для этого два простых инструмента.
«Именованные аргументы делают вызов самодокументируемым: код объясняет сам себя.»
Часто у функции есть параметры, у которых очевидное значение по умолчанию. Scala позволяет задать его прямо в объявлении — и тогда при вызове его можно опустить.
def greet(name: String, greeting: String = "Привет"): String =
s"$greeting, $name!"
println(greet("Аня")) // Привет, Аня!
println(greet("Аня", "Здравствуй")) // Здравствуй, Аня!Именованные аргументы
При вызове можно указывать имена параметров. Это снимает зависимость от порядка и делает код читаемым, особенно когда много булевых флагов.
def connect(host: String, port: Int = 80, secure: Boolean = false): String =
s"$host:$port secure=$secure"
println(connect("example.com"))
println(connect("example.com", secure = true)) // пропускаем port
println(connect(port = 443, host = "site.ru")) // любой порядокОбратите внимание: в третьем вызове порядок аргументов изменён, но благодаря именам всё корректно.
Сочетание возможностей
Параметры по умолчанию и именованные аргументы отлично работают вместе: задаёте только то, что отличается от умолчания.
def makeBox(width: Int = 1, height: Int = 1, depth: Int = 1): Int =
width * height * depth
println(makeBox()) // 1
println(makeBox(height = 5)) // 5, остальное по умолчанию
println(makeBox(2, 3, 4)) // 24Та же идея на Python ▶
# Те же возможности есть в Python
def greet(name, greeting="Привет"):
return f"{greeting}, {name}!"
def connect(host, port=80, secure=False):
return f"{host}:{port} secure={secure}"
print(greet("Аня")) # Привет, Аня!
print(connect("example.com", secure=True)) # именованный аргумент
print(connect(port=443, host="site.ru")) # любой порядокconnect(host, port = 80, secure = false)
\___/ \_____/
по умолчанию по умолчанию
вызов: connect("x", secure = true)
-> port берётся = 80 (умолчание)Как работает под капотом (JVM)
У JVM нет параметров по умолчанию. Scala генерирует за кулисами вспомогательные методы (с именами вроде greet$default$2), которые возвращают значение по умолчанию. При вызове greet("Аня") компилятор подставляет вызов этого вспомогательного метода для пропущенного аргумента. Именованные аргументы — чисто компиляторная функция: компилятор переставляет их в правильный порядок ещё до генерации байт-кода.
Частые ошибки
- Изменяемое значение по умолчанию. Значение по умолчанию вычисляется при каждом вызове, поэтому избегайте побочных эффектов в нём.
- Опираться на порядок там, где много флагов. Длинный список
true, false, trueнечитаем — используйте имена. - Слишком много параметров по умолчанию. Если их десятки, возможно, стоит сгруппировать их в case class (узнаем позже).
Best practices
- Давайте разумные умолчания для необязательных настроек.
- Используйте именованные аргументы для булевых флагов и для ясности на стороне вызова.
- Если параметров слишком много — подумайте о структуре-конфиге вместо длинной сигнатуры.
Дизайн удобных API
Параметры по умолчанию и именованные аргументы — это в первую очередь инструменты проектирования удобных интерфейсов. Хорошая функция должна быть простой в типичном случае и гибкой в редком. Значения по умолчанию обеспечивают первое: вызывающий указывает только то, что отличается от обычного, а остальное берётся разумным. Именованные аргументы обеспечивают второе: даже функцию с десятком настроек можно вызвать понятно, явно подписав каждый аргумент.
Особенно это спасает от так называемых «булевых ловушек». Вызов вида connect("host", true, false, true) нечитаем — невозможно понять, что означает каждый флаг, не открыв определение. С именованными аргументами тот же вызов превращается в самодокументируемый: connect("host", secure = true, retry = false). Код объясняет сам себя прямо в месте вызова, и читателю не нужно никуда переходить.
Есть и граница разумного. Если у функции становится слишком много параметров со значениями по умолчанию, это сигнал: возможно, их стоит сгруппировать в отдельную структуру-конфигурацию, которую мы научимся выражать через case-класс. Тогда вместо длинной сигнатуры появится один понятный объект настроек, который удобно создавать, копировать с изменениями и передавать. Умение вовремя сделать этот шаг отличает зрелый дизайн API от разросшейся сигнатуры.
В сочетании эти две возможности заметно повышают эргономику кода: типичный вызов остаётся коротким, а нетипичный — читаемым и явным. Это редкий случай, когда удобство для автора функции и удобство для её пользователя не противоречат друг другу, а складываются. Хорошо спроектированная сигнатура с разумными умолчаниями — это маленький подарок каждому, кто будет вызывать вашу функцию.
Итоги. Параметры по умолчанию задают значение, которое можно опустить, а именованные аргументы освобождают от порядка и проясняют вызов. Дальше — рекурсия и хвостовая оптимизация.