haskell-notes

(x *) :: b -> c

И правое сечение:

(*) :: a -> (b -> c),

x :: b

———————————

(* x) :: a -> c

Декомпозиция происходит в аргументах функции. С её помощью мы можем извлечь из составной

константы-дерева какую-нибудь часть или указать на какие константы мы реагируем в данном уравнении.

Ещё мы узнали о частичном применении. О том, что все функции в Haskell являются функциями одного

аргумента, которые возвращают константы или другие функции одного аргумента.

Мы потренировались в составлении неправильных выражений и посмотрели как компилятор на основе

правил применения узнаёт что они неправильные. Мы узнали, что такое ограничение мономорфизма и как

оно появляется. Также мы присмотрелись к рекурсивным функциям.

56 | Глава 3: Типы

Succ

not

Рис. 3.7: Конструкторы и синонимы

3.7 Упражнения

• Составьте в интерпретаторе как можно больше неправильных выражений и посмотрите на сообще-

ния об ошибках. Разберитесь почему выражение оказалось неправильным. Для этого проверьте типы с

помощью правил применения. Составьте несколько выражений, ведущих к ошибке из-за ограничения

мономорфизма.

• Потренируйтесь в интерпретаторе с функциями map, filter и foldr. Попробуйте их с самыми разными

функциями. Воспользуйтесь и теми функциями, что были определены в прошлой главе в тексте или в

упражнениях.

• В этой главе было много картинок и графических аналогий, попробуйте попрограммировать в картин-

ках. Нарисуйте определённые нами функции или какие-нибудь новые в виде деревьев. Например, это

можно сделать так. Мы будем отличать конструкторы от синонимов. Конструкторы будем рисовать в

одинарном кружке, а синонимы в двойном.

one

=

Nat

Succ

Zero

Рис. 3.8: Синоним-константа

Мы будем все функции писать также как и прежде, но вместо аргументов слева от знака равно и выра-

жений справа от знака равно, будем рисовать деревья.

Например, объявим простой синоним-константу (рис. 3.8). Мы будем дорисовывать сверху типы зна-

чений вместо объявления типа функции.

Несколько функций для списков. Извлечение первого элемента (рис. 3.9) и функция преобразования

всех элементов списка (рис. 3.10). Попробуйте в таком же духе определить несколько функций.

Упражнения | 57

head

[a]

=

a

:

x

x

Рис. 3.9: Функция извлечения первого элемента списка

map

a->b

[a]

=

[b]

[]

[]

f

map

a->b

[a]

=

[b]

:

:

f

x

xs

map

f

x

f

xs

Рис. 3.10: Функция преобразования элементов списка

58 | Глава 3: Типы

Глава 4

Декларативный и композиционный

стиль

В Haskell существует несколько встроенных выражений, которые облегчают построение функций и дела-

ют код более наглядным. Их можно разделить на два вида: выражения, которые поддерживают декларативный

стиль (declarative style) определения функций, и выражения которые поддерживают композиционный стиль

(expression style).

Что это за стили? В декларативном стиле определения функций больше похожи на математическую но-

тацию, словно это предложения языка. В композиционном стиле мы строим из маленьких выражений более

сложные, применяем к этим выражениям другие выражения и строим ещё большие.

В Haskell есть полноценная поддержка и того и другого стиля, поэтому конструкции которые мы рас-

смотрим в этой главе будут по смыслу дублировать друг друга. Выбор стиля скорее дело вкуса, существуют

приверженцы и того и другого стиля, поэтому разработчики Haskell не хотели никого ограничивать.

4.1 Локальные переменные

Вспомним формулу вычисления площади треугольника по трём сторонам:

v

S =

p · ( p ? a) · ( p ? b) · ( p ? c)

Где a, b и c – длины сторон треугольника, а p это полупериметр.

Как бы мы определили эту функцию теми средствами, что у нас есть? Наверное, мы бы написали так:

square a b c = sqrt (p a b c * (p a b c a) * (p a b c b) * (p a b c c))

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

Читай продолжение на следующей странице
Добавить комментарии