Выбрать главу

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

Ещё один очень простой пример: давайте напишем нашу собственную функцию 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

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

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

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

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

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

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

Заметили – мы повторили вычисление три раза? Операции копирования и вставки, да ещё повторенные трижды, – сущее наказание для программиста. Раз уж у нас вычисление повторяется три раза, было бы очень удобно, если бы мы могли вычислить его единожды, присвоить результату имя и использовать его, вместо того чтобы повторять вычисление. Можно переписать нашу функцию так:

bmiTell :: Double -> Double -> String bmiTell weight height

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

  | bmi <= 25.0 = "По части веса ты в норме.

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

  | bmi <= 30.0 = "Ты толстый!

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

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

  where bmi = weight / height ^ 2

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

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

bmiTell :: Double -> Double -> String

bmiTell weight height

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

  | bmi <= normal = "По части веса ты в норме.

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

  | bmi <= fat = "Ты толстый!

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

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

  where bmi = weight / height ^ 2

        skinny = 18.5

        normal = 25.0

        fat = 30.0

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

Область видимости декларации where

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