p a b c = (a + b + c) / 2
Согласитесь это не многим лучше чем решение в лоб:
square a b c = sqrt ((a+b+c)/2 * ((a+b+c)/2 - a) * ((a+b+c)/2 - b) * ((a+b+c)/2 - c)) И в том и в другом случае нам приходится дублировать выражения, нам бы хотелось чтобы определение
выглядело так же, как и обычное математическое определение:
square a b c = sqrt (p * (p - a) * (p - b) * (p - c))
p = (a + b + c) / 2
Нам нужно, чтобы p знало, что a, b и c берутся из аргументов функции square. В этом нам помогут
локальные переменные.
where-выражения
В декларативном стиле для этого предусмотрены where-выражения. Они пишутся так:
square a b c = sqrt (p * (p - a) * (p - b) * (p - c))
where p = (a + b + c) / 2
| 59
Или так:
square a b c = sqrt (p * (p - a) * (p - b) * (p - c)) where
p = (a + b + c) / 2
За определением функции следует специальное слово where, которое вводит локальные имена-
синонимы. При этом аргументы функции включены в область видимости имён. Синонимов может быть
несколько:
square a b c = sqrt (p * pa * pb * pc)
where p
= (a + b + c) / 2
pa = p - a
pb = p - b
pc = p - c
Отметим, что отступы обязательны. Haskell по отступам понимает, что эти выражения относятся к where.
Как и в случае объявления функций порядок следования локальных переменных в where-выражении не
важен. Главное чтобы в выражениях справа от знака равно мы пользовались именами из списка аргументов
исходной функции или другими определёнными именами. Локальные переменные видны только в пределах
той функции, в которой они вводятся.
Что интересно, слева от знака равно в where-выражениях можно проводить декомпозицию значений, так-
же как и в аргументах функции:
pred :: Nat -> Nat
pred x = y
where (Succ y) = x
Эта функция делает тоже самое что и функция
pred :: Nat -> Nat
pred (Succ y) = y
В where-выражениях можно определять новые функции а также выписывать их типы:
add2 x = succ (succ x)
where succ :: Int -> Int
succ x = x + 1
А можно и не выписывать, компилятор догадается:
add2 x = succ (succ x)
where succ x = x + 1
Но иногда это бывает полезно, при использовании классов типов, для избежания неопределённости при-
менения.
Приведём ещё один пример. Посмотрим на функцию фильтрации списков, она определена в Prelude:
filter :: (a -> Bool) -> [a] -> [a]
filter
p
[]
= []
filter
p
(x:xs) = if p x then x : rest else rest
where rest = filter p xs
Мы определили локальную переменную rest, которая указывает на рекурсивный вызов функции на остав-
шейся части списка.
where-выражения определяются для каждого уравнения в определении функции:
even :: Nat -> Bool
even Zero
= res
where res = True
even (Succ Zero) = res
where res = False
even x = even res
where (Succ (Succ res)) = x
Конечно в этом примере where не нужны, но здесь они приведены для иллюстрации привязки where-
выражения к данному уравнению. Мы определили три локальных переменных с одним и тем же именем.
where-выражения могут быть и у значений, которые определяются внутри where-выражений. Но лучше
избегать сильно вложенных выражений.
60 | Глава 4: Декларативный и композиционный стиль
let-выражения
В композиционном стиле функция вычисления площади треугольника будет выглядеть так:
square a b c = let p = (a + b + c) / 2
in
sqrt (p * (p - a) * (p - b) * (p - c))
Слова let и in – ключевые. Выгодным отличием let-выражений является то, что они являются обычными
выражениями и не привязаны к определённому месту как where-выражения. Они могут участвовать в любой
части обычного выражения:
square a b c = let p = (a + b + c) / 2
in
sqrt ((let pa = p - a in p * pa) *
(let pb = p - b
pc = p - c
in
pb * pc))
В этом проявляется их принадлежность композиционному стилю. let-выражения могут участвовать в
любом подвыражении, они также группируются скобками. А where-выражения привязаны к уравнениям в
определении функции.
Также как и в where-выражениях, в let-выражениях слева от знака равно можно проводить декомпозицию
значений.
pred :: Nat -> Nat
pred x = let (Succ y) = x
in
y
Определим функцию фильтрации списков через let:
filter :: (a -> Bool) -> [a] -> [a]
filter
p
[]
= []
filter
p
(x:xs) =
let rest = filter p xs
in
if p x then x : rest else rest
4.2 Декомпозиция
Декомпозиция или сопоставление с образцом позволяет выделять из составных значений, простейшие
значения с помощью которых они были построены
pred (Succ x) = x
и организовывать условные вычисления которые зависят от вида поступающих на вход функции значений
not True
= False
not False = True
Сопоставление с образцом
Декомпозицию в декларативном стиле мы уже изучили, это обычный случай разбора значений в аргу-
ментах функции. Рассмотрим одну полезную возможность при декомпозиции. Иногда нам хочется провести
декомпозицию и дать псевдоним всему значению. Это можно сделать с помощью специального символа @.
Например определим функцию, которая возвращает соседние числа для данного числа Пеано: