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

Давайте потренируемся с частичным применением в интерпретаторе. Для этого загрузим модуль Nat из

предыдущей главы:

Prelude> :l Nat

[1 of 1] Compiling Nat

( Nat. hs, interpreted )

Ok, modules loaded: Nat.

*Nat> let add = (+) :: Nat -> Nat -> Nat

*Nat> let addTwo = add (Succ (Succ Zero))

*Nat> :t addTwo

addTwo :: Nat -> Nat

*Nat> addTwo (Succ Zero)

Succ (Succ (Succ Zero))

*Nat> addTwo (addTwo Zero)

Succ (Succ (Succ (Succ Zero)))

Сначала мы ввели локальную переменную add, и присвоили ей метод (+) из класса Num для Nat. Нам

пришлось выписать тип функции, поскольку ghci не знает для какого экземпляра мы хотим определить этот

синоним. В данном случае мы подсказали ему, что это Nat. Затем с помощью частичного применения мы

объявили новый синоним addTwo, как мы видим из следующей строки это функция оного аргумента. Она

принимает любое значение типа Nat и прибавляет к нему двойку. Мы видим, что этой функцией можно

пользоваться также как и обычной функцией.

Попробуем выполнить тоже самое для функции с символьной записью имени:

*Nat> let add2 = (+) (Succ (Succ Zero))

*Nat> add2 Zero

Succ (Succ Zero)

Мы рассмотрели частичное применение для функций в префиксной форме записи. В префиксной фор-

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

существует два правила применения.

Это применение слева:

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

x :: a

-----------------------------

(x *) :: b -> c

И применение справа:

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

x :: b

-----------------------------

(* x) :: a -> c

Обратите внимание на типы аргумента и возвращаемого значения. Скобки в выражениях (x*) и (*x)

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

справа – второй.

Поясним на примере, для этого давайте возьмём функцию минус (-). Если мы напишем (2-) 1 то мы

получим 1, а если мы напишем (-2) 1, то мы получим -1. Проверим в интерпретаторе:

*Nat> (2-) 1

1

*Nat> (-2) 1

< interactive>:4:2:

Структура функций | 47

No instance for (Num (a0 -> t0))

arising from a use of syntactic negation

Possible fix: add an instance declaration for (Num (a0 -> t0))

In the expression: - 2

In the expression: (- 2) 1

In an equation for ‘it’: it = (- 2) 1

Ох уж этот минус. Незадача. Ошибка произошла из-за того, что минус является хамелеоном. Если мы

пишем -2, компилятор воспринимает минус как унарную операцию, и думает, что мы написали константу

минус два. Это сделано для удобства, но иногда это мешает. Это единственное такое исключение в Haskell.

Давайте введём новый синоним для операции минус:

*Nat> let (#) = (-)

*Nat> (2#) 1

1

*Nat> (#2) 1

-1

Эти правила левого и правого применения работают и для буквенных имён в инфиксной форме записи:

*Nat> let minus = (-)

*Nat> (2 ‘minus‘ ) 1

1

*Nat> ( ‘minus‘ 2) 1

-1

Так если мы хотим на лету получить новую функцию, связав в функции второй аргумент мы можем

написать:

... = ... ( ‘fun‘ x) ...

Частичное применение для функций в инфиксной форме записи называют сечением (section), они бывают

соответственно левыми и правыми.

Связь с логикой

Отметим связь основного правила применения с Modus Ponens, известным правилом вывода в логике:

a -> b,

a

-------------

b

Оно говорит о том, что если у нас есть выражение из a следует b и мы знаем, что a истинно, мы смело

можем утверждать, что b тоже истинно. Если перевести это правило на Haskell, то мы получим: Если у нас

определена функция типа a -> b и у нас есть значение типа a, то мы можем получить значение типа b.

Декомпозиция и сопоставление с образцом

Декомпозиция применяется слева от знака равно, при этом наша задача состоит в том, чтобы опознать

дерево определённого вида и выделить из него некоторые поддеревья. Мы уже пользовались декомпозицией

много раз в предыдущих главах, давайте выпишем примеры декомпозиции:

not :: Bool -> Bool

not True

= ...

not False

= ...

xor :: Bool -> Bool -> Bool

xor a b = ...

show :: Show a => a -> String

show (Time h m s) = ...

addZero :: String -> String

addZero (a:[])

= ...

addZero as

= ...

(*)

a

Zero

= ...

(*)

a

(Succ b)

= ...

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

Декомпозицию можно проводить в аргументах функции. Там мы видим строчную запись дерева, в узлах

стоят конструкторы (начинаются с большой буквы), переменные (с маленькой буквы) или символ безразлич-

ной переменой (подчёркивание).

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

ного уравнения. Так уравнение

not True

= ...

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

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

is7 :: Nat -> Bool

is7

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

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

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

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

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

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