Охранные выражения обозначаются вертикальными чёрточками после имени и параметров функции. Обычно они печатаются с отступом вправо и начинаются с одной позиции. Охранное выражение должно иметь тип 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
ПРИМЕЧАНИЕ. Можно не только вызывать функции с помощью обратных апострофов, но и определять их так же. Иногда такую запись легче читать.
Где же ты, where?!
Программисты обычно стараются избегать многократного вычисления одних и тех же значений. Гораздо проще один раз вычислить что-то, а потом сохранить его значение. В императивных языках программирования эта проблема решается сохранением результата вычислений в переменной. В данном разделе вы научитесь использовать ключевое слово where
для сохранения результатов промежуточных вычислений примерно с той же функциональностью.
В прошлом разделе мы определили вычислитель ИМТ и «ругалочку» на его основе таким образом:
bmiTell :: Double -> Double -> String
bmiTell weight height