Maybe: безопасность без исключений

Maybe — это честный способ сказать «значение может отсутствовать». Никаких null, никаких внезапных падений.
Функция, которая может не найти результат, возвращает Maybe: Just x если нашла, Nothing если нет. И компилятор заставит вас обработать оба случая.

Во многих языках «отсутствие значения» выражают через null — и забывают проверить, получая падение. Haskell решает это типом Maybe, который встроен в систему типов:

data Maybe a = Nothing | Just a

Значение типа Maybe a — это либо Nothing (ничего нет), либо Just x (есть значение x). Тип честно сообщает, что результата может не быть, и компилятор не даст про это забыть.

Безопасные функции

Сравните «опасный» head и безопасный вариант:

safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing          -- деление на ноль — нет результата
safeDiv x y = Just (x `div` y)
safeDiv 10 2  ->  Just 5      (всё хорошо)
safeDiv 10 0  ->  Nothing     (нет ответа, но НЕ падение)

Как обработать Maybe

Чтобы достать значение, разбирают оба случая — сопоставлением с образцом или функцией maybe:

describe :: Maybe Int -> String
describe Nothing  = "нет значения"
describe (Just n) = "значение: " ++ show n

-- удобные хелперы:
fromMaybe :: a -> Maybe a -> a    -- значение или запасное
-- fromMaybe 0 (Just 5)  == 5
-- fromMaybe 0 Nothing   == 0

Главное: компилятор требует обработать Nothing. Невозможно «случайно» использовать отсутствующее значение как настоящее — целый класс ошибок с null просто исчезает.

В Python отсутствие значения часто моделируют через None или Optional — близкая идея, но без принуждения проверять:

# Та же идея на Python: Optional вместо null
from typing import Optional

def safe_div(x: int, y: int) -> Optional[int]:
    if y == 0:
        return None          # ~ Nothing
    return x // y            # ~ Just (x // y)

print(safe_div(10, 2))       # 5
print(safe_div(10, 0))       # None

# обработка обоих случаев
def describe(v: Optional[int]) -> str:
    if v is None:
        return "нет значения"
    return f"значение: {v}"

print(describe(safe_div(10, 0)))   # нет значения
# Разница: Haskell ЗАСТАВЛЯЕТ обработать Nothing, Python — лишь напоминает

Конец эпохи null

Тип Maybe в одиночку закрывает целый класс ошибок, который десятилетиями преследует другие языки, — пресловутую «ошибку на миллиард долларов», то есть разыменование null. В Haskell отсутствие значения не прячется за внешне обычным указателем, а становится частью типа: увидев Maybe a в сигнатуре, вы сразу знаете, что результата может не быть, и компилятор не даст про это забыть. Использовать Just x там, где ждут x, просто не получится — значение нужно сперва распаковать, разобрав оба случая. Это превращает потенциальное падение в продакшене в обязательную проверку на этапе компиляции. Для удобства есть целый набор хелперов — fromMaybe, maybe, mapMaybe, catMaybes, — которые избавляют от ручного разбора в типовых ситуациях. А когда таких значений в цепочке много, на помощь приходит монадическая запись, превращающая лестницу проверок в линейный код.

Как это мыслить

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

Стоит увидеть Maybe и в более широком свете: это первый из «контекстов вычисления», с которыми вы будете работать дальше. Maybe представляет контекст «возможного отсутствия», и именно на нём проще всего впервые почувствовать, что такое функтор и монада. Когда вы научитесь применять функцию «внутрь» Maybe через fmap, не разбирая обёртку вручную, или связывать цепочки шагов, каждый из которых может дать Nothing, абстрактные понятия из финального раздела наполнятся конкретным смыслом. Поэтому потратьте время на то, чтобы Maybe стал для вас совершенно привычным: это не просто полезный тип сам по себе, но и удобнейший трамплин к пониманию монад, на котором паттерн «вычисление в контексте» виден в самой простой и наглядной форме.

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

  • Пытаться использовать Just x как x. Значение завёрнуто; его нужно «распаковать» сопоставлением или хелпером.
  • Забыть случай Nothing. Компилятор предупредит — не игнорируйте.
  • Цепочки вложенных case. Когда Maybe много, это громоздко — следующий урок покажет, как их связать монадически.

Best practices

  • Возвращайте Maybe вместо «магических» значений вроде -1 при неудаче.
  • Используйте fromMaybe, maybe, mapMaybe вместо ручного разбора, где можно.
  • Для ошибок с пояснением рассмотрите Either (следующий урок).

Итог. Maybe a — это Nothing или Just x: типобезопасная замена null. Функции, которые могут не вернуть результат, делают это явно, а компилятор требует обработать оба случая — и ошибки «забыл проверить» исчезают.

Проверьте себя
1. Из каких двух конструкторов состоит тип Maybe a?
ATrue и False
BNothing и Just a
CLeft и Right
DSome и None
2. В чём преимущество Maybe перед null?
AMaybe быстрее
BТип честно сообщает о возможном отсутствии значения, и компилятор требует обработать случай Nothing
CMaybe занимает меньше памяти
DMaybe работает только с числами