haskell-notes

((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 позволяют очень легко комбинировать различ-

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

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

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

Мы определим экземпляр Num для функций, которые возвращают числа. Смысл этих операций заключается в

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

падает по типу. Например для того чтобы умножить функции t -> t+2 и t -> t+3 мы составляем новую

функцию t -> (t+2) * (t+3), которая получает на вход значение t применяет его к каждой из функций и

затем умножает результаты:

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

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162