haskell-notes

прагмы RULES. Сравните показатели производительности с правилами и без (для этого скомпилируйте

дважды с флагом O и без) на тестовом выражении:

main = print $ sumL $

mapL (x -> x 1000) $ mapL (+100) $ mapL (*2) $ genL 0 1e6

Функция sumL находит сумму элементов в списке, функция genL генерирует список чисел с единичным

шагом от первого аргумента до второго.

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

mapL f (mapL g xs)

= …

foldrL cons nil (mapL f xs)

= …

• Откройте исходный код Prelude и присмотритесь к различным прагмам. Попытайтесь понять почему

они там используются.

180 | Глава 10: Реализация Haskell в GHC

Глава 11

Ленивые чудеса

В прошлой главе мы узнали, что такое ленивые вычисления. В этой главе мы посмотрим чем они хо-

роши. С ними можно делать невозможные вещи. Обращаться к ещё не вычисленным значениям, работать с

бесконечными данными.

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

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

задачи по-меньше, потом собираем из них решения, из этих решений собираем другие решения и вот уже

готова программа. Но мы решаем задачу не на листочке, нам необходимо объяснить её компьютеру. И тот

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

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

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

Об этом говорит Джон Хьюз (John Huges) в статье “Why functional programming matters”. Он приводит та-

кую метафору. Если мы делаем стул и у нас нет хорошего клея. Единственное что нам остаётся это вырезать

из дерева стул целиком. Это невероятно трудная задача. Гораздо проще сделать отдельные части и потом

собрать вместе. Функциональные языки программирования предоставляют два новых вида “клея”. Это функ-

ции высшего порядка и ленивые вычисления. В статье можно найти много примеров. Некоторые из них мы

рассмотрим в этой главе.

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

помощью мы можем параметризовать функцию другой функцией (поведением). Они дают нам возможность

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

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

выполнять это вручную.

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

решаем задачу мы пытаемся разложить её на простейшие составляющие. При этом часто оказывается, что

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

букет решений, там где искали одно.

11.1 Численные методы

Рассмотрим несколько численных методов. Все эти методы построены на понятии сходимости. У нас есть

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

что промежуточные решения будут всё ближе и ближе к итоговому.

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

Так же как мы делали это в прошлой главе, когда искали корни уравнения методом неподвижной точки. Эти

примеры взяты из статьи “Why functional programming matters” Джона Хьюза.

Дифференцирование

Найдём производную функции в точке. Посмотрим на математическое определение производной:

f ( x + h) ? f ( x)

f ( x) = lim

h> 0

h

Производная это предел последовательности таких отношений, при h стремящемся к нулю. Если предел

сходится, то производная определена. Для того чтобы решить эту задачу мы начнём с небольшого значе-

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

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

Этот процесс напоминает то, что мы делали при поиске корня уравнения методом неподвижной точки.

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

| 181

converge :: (Ord a, Num a) => a -> [a] -> a

converge eps (a:b:xs)

| abs (a b) <= eps

= a

| otherwise

= converge eps (b:xs)

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

вычисляет промежуточные решения:

easydiff :: Fractional a => (a -> a) -> a -> a -> a

easydiff f x h = (f (x + h) f x) / h

Мы возьмём начальное значение шага и будем последовательно уменьшать его вдвое:

halves = iterate (/2)

Соберём все части вместе:

diff :: (Ord a, Fractional a) => a -> a -> (a -> a) -> a -> a

diff h0 eps f x = converge eps $ map (easydiff f x) $ iterate (/2) h0

where easydiff f x h = (f (x + h) f x) / h

Сохраним эти определения в отдельном модуле и найдём производную какой-нибудь функции. Проте-

стируем решение на экспоненте. Известно, что производная экспоненты равна самой себе:

*Numeric> let exp’ = diff 1 1e-5 exp

*Numeric> let test x = abs $ exp x exp’ x

Страницы: 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