>cylinder :: Double -> Double -> Double
>cylinder r h =
> let sideArea = 2 * pi * r * h
> topArea = pi * r 2
> in sideArea + 2 * topArea
Общее выражение выглядит так: >let <определения> in <выражение>
. Имена, которые вы определили в части >let
, видимы в выражении после ключевого слова >in
. Как видите, мы могли бы воспользоваться ключевым словом >where
для той же цели. Обратите внимание, что имена также выровнены по одной вертикальной позиции. Ну и какая разница между определениями в секциях >where
и >let
? Просто, похоже, в секции >let
сначала следуют определения, а затем выражение, а в секции >where
– наоборот.
На самом деле различие в том, что определения >let
сами по себе являются выражениями. Определения в секциях >where
– просто синтаксические конструкции. Если нечто является выражением, то у него есть значение. >"Фуу!"
– это выражение, и >3+5
– выражение, и даже >head [1,2,3]
. Это означает, что определение >let
можно использовать практически где угодно, например:
>ghci> 4 * (let a = 9 in a + 1) + 2
>42
Ключевое слово >let
подойдёт для определения локальных функций:
>ghci> [let square x = x * x in (square 5, square 3, square 2)]
>[(25,9,4)]
Если нам надо привязать значения к нескольким переменным в одной строке, мы не можем записать их в столбик. Поэтому мы разделяем их точкой с запятой.
>ghci> (let a = 10; b = 20 in a*b, let foo="Эй, "; bar = "там!" in foo ++ bar)
>(200,"Эй, там!")
Как мы уже говорили ранее, определения в секции >let
могут использоваться при сопоставлении с образцом. Они очень полезны, к примеру, для того, чтобы быстро разобрать кортеж на элементы и привязать значения элементов к переменным, а также в других подобных случаях.
>ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100
>600
Если определения >let
настолько хороши, то почему бы только их всё время и не использовать? Ну, так как это всего лишь выражения, причём с локальной областью видимости, то их нельзя использовать в разных охранных выражениях. К тому же некоторые предпочитают, чтобы их переменные вычислялись после использования в теле функции, а не до того. Это позволяет сблизить тело функции с её именем и типом, что способствует большей читабельности.
Выражения let в генераторах списков
Давайте перепишем наш предыдущий пример, который обрабатывал списки пар вида (вес, рост), чтобы он использовал секцию >let
в выражении вместо того, чтобы определять вспомогательную функцию в секции >where
.
>calcBmis :: [(Double, Double)] -> [Double]
>calcBmis xs = [bmi | (w, h) <– xs, let bmi = w / h 2]
Мы поместили выражение >let
в генератор списка так, словно это предикат, но он не фильтрует список, а просто определяет имя. Имена, определённые в секции