Option: безопасная работа с отсутствием

Option — это ответ Scala на «ошибку на миллиард долларов»: вместо коварного null отсутствие значения становится видимым в типе.

«Option честно говорит: значения может не быть. И компилятор не даст вам об этом забыть.»

Во многих языках отсутствие значения обозначают null, что приводит к внезапным падениям. Scala предлагает Option — контейнер, который либо содержит значение (Some(x)), либо пуст (None). Отсутствие становится частью типа, и его нельзя проигнорировать.

val found: Option[Int] = Some(42)
val empty: Option[Int] = None

def findUser(id: Int): Option[String] =
  if id == 1 then Some("Аня") else None

println(findUser(1))   // Some(Аня)
println(findUser(99))  // None
Option[String]
   /        \
 Some("Аня")    None
 значение есть   значения нет
   (но НЕ null — это честный тип)

Безопасное извлечение через getOrElse

Чтобы достать значение, не падая на пустоте, используют getOrElse — он даёт запасное значение для None.

val name = findUser(99).getOrElse("Гость")
println(name)   // Гость

val name2 = findUser(1).getOrElse("Гость")
println(name2)  // Аня

map и flatMap на Option

Главная красота: Option ведёт себя как коллекция из 0 или 1 элемента. map преобразует значение, если оно есть, и ничего не делает для None — проверок на пустоту не нужно.

val len: Option[Int] = findUser(1).map(_.length)
println(len)   // Some(3)

val none: Option[Int] = findUser(99).map(_.length)
println(none)  // None — map просто пропустил

Option в for-выражении

def parseAge(s: String): Option[Int] = s.toIntOption

val result = for
  a <- parseAge("25")
  b <- parseAge("5")
yield a + b
println(result)   // Some(30)

val bad = for
  a <- parseAge("25")
  b <- parseAge("xx")   // None
yield a + b
println(bad)   // None — вся цепочка свернулась в None

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

# Аналог Option на Python — None плюс явные проверки
def find_user(uid):
    return "Аня" if uid == 1 else None   # как Some/None

name = find_user(99) or "Гость"          # как getOrElse
print(name)                              # Гость

# map на Option = проверка перед преобразованием
user = find_user(1)
length = len(user) if user is not None else None   # как .map(_.length)
print(length)                            # 3

# цепочка с возможным None
def parse_int(s):
    try: return int(s)
    except ValueError: return None
a, b = parse_int("25"), parse_int("xx")
result = a + b if a is not None and b is not None else None
print(result)                            # None

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

Option — это sealed-иерархия: Some[A] (case-класс с одним полем) и объект None. То есть это обычные объекты JVM, а не магия. map на Option реализован через паттерн-матчинг: для Some(x) применяет функцию и оборачивает результат в Some, для None возвращает None. Поскольку Option имеет map и flatMap, он работает в for-выражениях — компилятор переписывает их так же, как для коллекций.

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

  • Вызывать .get без проверки. На None это бросит исключение — почти как null. Предпочитайте getOrElse или map.
  • Городить if option.isDefined. Часто чище использовать map/getOrElse или паттерн-матчинг.
  • Возвращать null из метода. В Scala 3 для «может не быть значения» используйте Option.

Best practices

  • Возвращайте Option вместо null, когда значение может отсутствовать.
  • Извлекайте значение через getOrElse, map или match, а не через .get.
  • Соединяйте несколько Option в for-выражении — пустота автоматически прервёт цепочку.

Конец эпохи null

Тони Хоар, изобретатель ссылки null, назвал её своей «ошибкой на миллиард долларов» — за бесчисленные падения программ, которые она вызвала. Option — это структурное решение проблемы. Вместо того чтобы любое значение тайно могло оказаться null, отсутствие выражается явным типом, который виден в сигнатурах. Функция, возвращающая Option[String], честно предупреждает: значения может не быть, обработай это.

Самое элегантное в Option — что он ведёт себя как коллекция из нуля или одного элемента. Это позволяет применять к нему те же map, filter и for, что и к спискам, без единой проверки на пустоту. Цепочка преобразований над Option автоматически останавливается, как только встретит None. Так код, работающий с возможным отсутствием, выглядит так же чисто, как код с гарантированными значениями — а компилятор всё это время следит, что вы не забыли про пустой случай.

Привыкайте читать тип Option в сигнатуре как явное предупреждение: «здесь значения может не быть». Это превращает работу с отсутствием из источника внезапных падений в обычную, контролируемую часть логики. Со временем отсутствие null в Scala-коде начинает ощущаться как глоток свежего воздуха — целый класс ночных дежурств по разбору NullPointerException просто исчезает.

Итоги. Option (Some/None) заменяет null, делая отсутствие явным; getOrElse, map и for работают с ним безопасно. Дальше — обработка ошибок через Try и Either.

Проверьте себя
1. Какую проблему решает Option?
AМедленные вычисления
BОпасность null: отсутствие значения становится видимым в типе как None
CНехватку памяти
DОтсутствие классов
2. Что вернёт findUser(99).map(_.length), если findUser(99) равно None?
AОшибку
B0
CNone — map ничего не делает для None
DSome(0)