Где же ты, 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 не поймёт, что все они – часть одного блока определений.