задать приоритет выше применения, это значит, что операция “пробел” будет всегда выполняться первой.
Ассоциативность используется для группировки одинаковых операций, например мы видим:
1+
2+3+4Как нам быть? Мы можем группировать скобки слева направо:
((1+
2)+3)+4Или справа налево:
1+
(2+(3+4))Ответ на этот вопрос даёт ассоциативность, она бывает левая и правая. Например операции (+
) (-) и (*)являются лево-ассоциативными, а операция возведения в степень (^
) является право-ассоциативной.1 +
2 + 3 == (1 + 2) + 31 ^
2 ^ 3 ==1 ^
(2 ^ 3)Приоритет функции можно узнать в интерпретаторе с помощью команды :
i:*FunNat> :
m PreludePrelude> :
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 * 37
*Fixity>
1 +++ 2 *** 39
*Fixity>
1 - 2 - 3-
4*Fixity>
1 ‘neg‘ 2 ‘neg‘ 32
Посмотрим как это вычислялось:
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 -> bf $
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 40ns :: 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 2Prelude> let
xs = reverse $ map ((+1) . (*10)) $ filter even $ ns 20Prelude>
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 позволяют очень легко комбинировать различ-ные функции. Бесточечный стиль записи функций превращает функции в простые значения или значения-