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

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

Ассоциативность используется для группировки одинаковых операций, например мы видим:

1+2+3+4

Как нам быть? Мы можем группировать скобки слева направо:

((1+2)+3)+4

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

1+(2+(3+4))

Ответ на этот вопрос даёт ассоциативность, она бывает левая и правая. Например операции (+) (-) и (*)

являются лево-ассоциативными, а операция возведения в степень (^) является право-ассоциативной.

1 + 2 + 3 == (1 + 2) + 3

1 ^ 2 ^ 3 ==

1 ^ (2 ^ 3)

Приоритет функции можно узнать в интерпретаторе с помощью команды :i:

*FunNat> :m Prelude

Prelude> :i (+)

class (Eq a, Show a) => Num a where

(+) :: a -> a -> a

...

-- Defined in GHC.Num

infixl 6 +

Prelude> :i (*)

class (Eq a, Show a) => Num a where

...

(*) :: a -> a -> a

...

-- Defined in GHC.Num

infixl 7 *

Prelude> :i (^)

(^) :: (Num a, Integral b) => a -> b -> a

-- Defined in GHC.Real

infixr 8 ^

76 | Глава 5: Функции высшего порядка

Приоритет указывается в строчках infixl 6 + и infixl 7 *. Цифра указывает на старшинство операции,

а суффикс l (от англ. left – левая) или r (от англ. right – правая) на ассоциативность.

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

коде:

module Fixity where

import Prelude(Num(.. ))

infixl 4 ***

infixl 5 +++

infixr 5 ‘neg‘

(***) = (*)

(+++) = (+)

neg

= (-)

Мы ввели новые операции и поменяли старшинство операций сложения и умножения местами и изме-

нили ассоциативность у вычитания. Проверим в интерпретаторе:

Prelude> :l Fixity

[1 of 1] Compiling Fixity

( Fixity. hs, interpreted )

Ok, modules loaded: Fixity.

*Fixity> 1 + 2 * 3

7

*Fixity> 1 +++ 2 *** 3

9

*Fixity> 1 - 2 - 3

-4

*Fixity> 1 ‘neg‘ 2 ‘neg‘ 3

2

Посмотрим как это вычислялось:

1

+

2

*

3

==

1

+

(2

*

3)

1

+++

2

*** 3

==

(1

+++

2)

***

3

1

-

2

-

3

==

(1

-

2)

-

3

1 ‘neg‘ 2 ‘neg 3‘ ==

1 ‘neg‘ (2

‘neg‘ 3)

Также в Haskell есть директива infix это тоже самое, что и infixl.

Приоритет функции композиции

Посмотрим на приоритет функции композиции:

Prelude> :i (. )

(. ) :: (b -> c) -> (a -> b) -> a -> c

-- Defined in GHC.Base

infixr 9 .

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

стиле. Такая функция похожа на конвейер функций:

fun a = fun1 a . fun2 (x1 + x2) . fun3 . (+x1)

Приоритет функции применения

Теперь посмотрим на полное определение функции применения:

infixr 0 $

($) :: (a -> b) -> a -> b

f $ x

=

f x

Ответ на вопрос о полезности этой функции кроется в её приоритете. Ей назначен самый низкий прио-

ритет. Она будет исполняться в последнюю очередь. Очень часто возникают ситуации вроде:

Приоритет инфиксных операций | 77

foldNat zero succ (Succ b) = succ (foldNat zero succ b)

С помощью функции применения мы можем переписать это определение так:

foldNat zero succ (Succ b) = succ $ foldNat zero succ b

Если бы мы написали без скобок:

... = succ foldNat zero succ b

То выражение было бы сгруппировано так:

... = (((succ foldNat) zero) succ) b

Но поскольку мы поставили барьер в виде операции ($) с низким приоритетом, группировка скобок

произойдёт так:

... = (succ $ ((foldNat zero) succ) b)

Это как раз то, что нам нужно. Преимущество этого подхода проявляется особенно ярко если у нас

несколько вложенных функций на конце выражения:

xs :: [Int]

xs = reverse $ map ((+1) . (*10)) $ filter even $ ns 40

ns :: Int -> [Int]

ns 0

= []

ns n

= n : ns (n - 1)

В списке xs мы сначала создаём в функции ns убывающий список чисел, затем оставляем лишь чётные,

потом применяем два арифметических действия ко всем элементам списка, затем переворачиваем список.

Проверим работает ли это в интерпретаторе, заодно поупражняемся в композиционном стиле:

Prelude> let ns n = if (n == 0) then [] else n : ns (n - 1)

Prelude> let even x = 0 == mod x 2

Prelude> let xs = reverse $ map ((+1) . (*10)) $ filter even $ ns 20

Prelude> xs

[21,41,61,81,101,121,141,161,181,201]

Если бы не функция применения нам пришлось бы написать это выражение так:

xs = reverse (map ((+1) . (*10)) (filter even (ns 40)))

5.3 Функциональный калькулятор

Мне бы хотелось сделать акцент на одном из вступительных предложений этой главы:

За счёт развитых средств составления новых функций в Haskell пользователь определяет лишь

базовые функции, получая остальные “на лету” применением двух-трёх операций, это выглядит

примерно как (2+3)*5, где вместо чисел стоят базовые функции, а операции + и * составляют

новые функции из простейших.

Такие обобщённые функции как id, const, (. ), map filter позволяют очень легко комбинировать различ-

ные функции. Бесточечный стиль записи функций превращает функции в простые значения или значения-

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

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

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

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

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

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