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