| 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
не являются общими для различных образцов данной функции. Предположим, что мы хотим написать функцию, которая принимает на вход имя человека и, если это имя ей знакомо, вежливо его приветствует, а если нет – тоже приветствует, но несколько грубее. Первая попытка может выглядеть примерно так:
greet :: String -> String
greet "Хуан" = niceGreeting ++ " Хуан!"
greet "Фернандо" = niceGreeting ++ " Фернандо!"
greet name = badGreeting ++ " " ++ name
where niceGreeting = "Привет! Так приятно тебя увидеть,"
badGreeting = "О, чёрт, это ты,"
Однако эта функция работать не будет, так как имена, введённые в блоке where
, видимы только в последнем варианте определения функции. Исправить положение может только глобальное определение функций niceGreeting
и badGreeting
, например:
badGreeting :: String
badGreeting = "О, чёрт, это ты,"
niceGreeting :: String
niceGreeting = "Привет! Так приятно тебя увидеть,"
greet :: String -> String
greet "Хуан" = niceGreeting ++ " Хуан!"