Эй, стража!

В то время как образцы – это способ убедиться, что значение соответствует некоторой форме, и разобрать его на части, сторожевые условия (охрана, охранные выражения) – это способ проверить истинность некоторого свойства значения или нескольких значений, переданных функции. Тут можно провести аналогию с условным выражением if: оно работает схожим образом. Однако охранные выражения гораздо легче читать, если у вас имеется несколько условий; к тому же они отлично работают с образцами.

Вместо того чтобы объяснять их синтаксис, давайте просто напишем функцию с использованием охранных условий. Эта простая функция будет оценивать вас на основе ИМТ (индекса массы тела). Ваш ИМТ равен вашему весу, разделённому на квадрат вашего роста.

Если ваш ИМТ меньше 18,5, можно считать вас тощим. Если ИМТ составляет от 18,5 до 25, ваш вес в пределах нормы. От 25 до 30 – вы полненький; более 30 – тучный. Запишем эту функцию (мы не будем рассчитывать ИМТ, функция принимает его как параметр и ругнёт вас соответственно).

bmiTell :: Double -> String

bmiTell bmi

  | bmi <= 18.5 = "Слышь, эмо, ты дистрофик!"

  | bmi <= 25.0 = "По части веса ты в норме. Зато, небось, уродец!"

  | bmi <= 30.0 = "Ты толстый! Сбрось хоть немного веса!"

  | otherwise = "Мои поздравления, ты жирный боров!"

Охранные выражения обозначаются вертикальными чёрточками после имени и параметров функции. Обычно они печатаются с отступом вправо и начинаются с одной позиции. Охранное выражение должно иметь тип Bool. Если после вычисления условие имеет значение True, используется соответствующее тело функции. Если вычисленное условие ложно, проверка продолжается со следующего условия, и т. д.

Если мы вызовем эту функцию с параметром 24.3, она вначале проверит, не является ли это значение меньшим или равным 18.5. Так как охранное выражение на данном значении равно False, функция перейдёт к следующему варианту. Проверяется следующее условие, и так как 24.3 меньше, чем 25.0, будет возвращена вторая строка.

Это очень напоминает большие деревья условий if – else в императивных языках программирования – только такой способ записи значительно лучше и легче для чтения. Несмотря на то что большие деревья условий if – else обычно не рекомендуется использовать, иногда задача представлена в настолько разрозненном виде, что просто невозможно обойтись без них. Охранные выражения – прекрасная альтернатива для таких задач.

Во многих случаях последним охранным выражением является otherwise («иначе»). Значение otherwise определяется просто: otherwise = True; такое условие всегда истинно. Работа условий очень похожа на то, как работают образцы, но образцы проверяют входные данные, а охранные выражения могут производить любые проверки.

Если все охранные выражения ложны (и при этом мы не записали otherwise как последнее условие), вычисление продолжается со следующей строки определения функции. Вот почему сопоставление с образцом и охранные выражения так хорошо работают вместе. Если нет ни подходящих условий, ни клозов, будет сгенерирована ошибка времени исполнения.

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

bmiTell :: Double -> Double -> String

bmiTell weight height

  | weight / height ^ 2 <= 18.5 = "Слышь, эмо, ты дистрофик!"

  | weight / height ^ 2 <= 25.0 = "По части веса ты в норме.

                                   Зато, небось, уродец!"

  | weight / height ^ 2 <= 30.0 = "Ты толстый!

                                   Сбрось хоть немного веса!"

  | otherwise = "Мои поздравления, ты жирный боров!"

Ну-ка проверим, не толстый ли я…

ghci> bmiTell 85 1.90

"По части веса ты в норме. Зато, небось, уродец!"

Ура! По крайней мере, я не толстый! Правда, Haskell обозвал меня уродцем. Ну, это не в счёт.

ПРИМЕЧАНИЕ. Обратите внимание, что после имени функции и её параметров нет знака равенства до первого охранного выражения. Многие новички ставят этот знак, что приводит к ошибке.

Ещё один очень простой пример: давайте напишем нашу собственную функцию max. Если вы помните, она принимает два значения, которые можно сравнить, и возвращает большее из них.

max' :: (Ord a) => a –> a –> a

max' a b

  | a <= b = b

  | otherwise = a

Продолжим: напишем нашу собственную функцию сравнения, используя охранные выражения.

myCompare :: (Ord a) => a –> a –> Ordering

a `myCompare` b

  | a == b = EQ

  | a <= b = LT

  | otherwise = GT

ghci> 3 `myCompare` 2

GT

ПРИМЕЧАНИЕ. Можно не только вызывать функции с помощью обратных апострофов, но и определять их так же. Иногда такую запись легче читать.