Читаем Учебник по Haskell полностью

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

Сопоставление с образцом

Декомпозицию в декларативном стиле мы уже изучили, это обычный случай разбора значений в аргу-

ментах функции. Рассмотрим одну полезную возможность при декомпозиции. Иногда нам хочется провести

декомпозицию и дать псевдоним всему значению. Это можно сделать с помощью специального символа @.

Например определим функцию, которая возвращает соседние числа для данного числа Пеано:

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных