помощью мы можем параметризовать функцию другой функцией (поведением). Они дают нам возможность
выделять сложные закономерности и собирать их в функции. Ленивые вычисления же предназначены для
склеивания больших программ. Они синхронизируют выполнение подзадач, избавляя нас от необходимости
выполнять это вручную.
Эта идея разбиения программы на независимые части приводит нас к понятию модульности. Когда мы
решаем задачу мы пытаемся разложить её на простейшие составляющие. При этом часто оказывается, что
эти составляющие применимы не только для нашей задачи, но и для многих других. Мы получаем целый
букет решений, там где искали одно.
11.1 Численные методы
Рассмотрим несколько численных методов. Все эти методы построены на понятии сходимости. У нас есть
последовательность решений и она сходится к одному решению, но мы не знаем когда. Мы только знаем,
что промежуточные решения будут всё ближе и ближе к итоговому.
Поскольку у нас ленивый язык мы сначала построим все возможные решения, а затем выберем итоговое.
Так же как мы делали это в прошлой главе, когда искали корни уравнения методом неподвижной точки. Эти
примеры взяты из статьи “Why functional programming matters” Джона Хьюза.
Дифференцирование
Найдём производную функции в точке. Посмотрим на математическое определение производной:
f ( x + h ) − f ( x )
f ( x ) = lim
h→ 0
h
Производная это предел последовательности таких отношений, при h стремящемся к нулю. Если предел
сходится, то производная определена. Для того чтобы решить эту задачу мы начнём с небольшого значе-
ния h и будем постепенно уменьшать его, вычисляя промежуточные значения производной. Как только они
перестанут сильно изменяться мы будем считать, что мы нашли предел последовательности
Этот процесс напоминает то, что мы делали при поиске корня уравнения методом неподвижной точки.
Мы можем взять из того решения функцию определения сходимости последовательности:
| 181
converge ::( Orda, Numa) =>a ->[a] ->a
converge eps (a :b :xs)
|abs (a -b) <=eps
=a
|otherwise
=converge eps (b :xs)
Теперь осталось только создать последовательность значений производных. Напишем функцию, которая
вычисляет промежуточные решения:
easydiff :: Fractionala =>(a ->a) ->a ->a ->a
easydiff f x h =(f (x +h) -f x) /h
Мы возьмём начальное значение шага и будем последовательно уменьшать его вдвое:
halves =iterate ( /2)
Соберём все части вместе:
diff ::( Orda, Fractionala) =>a ->a ->(a ->a) ->a ->a
diff h0 eps f x =converge eps $map (easydiff f x) $iterate ( /2) h0
whereeasydiff f x h =(f (x +h) -f x) /h
Сохраним эти определения в отдельном модуле и найдём производную какой-нибудь функции. Проте-
стируем решение на экспоненте. Известно, что производная экспоненты равна самой себе:
*Numeric> letexp’ =diff 1 1e-5 exp
*Numeric> lettest x =abs $exp x -exp’ x
*Numeric>test 2
1.4093421286887065e-5
*Numeric>test 5
1.767240203776055e-5
Интегрирование
Теперь давайте поинтегрируем функции одного аргумента. Интеграл это площадь кривой под графиком
функции. Если бы кривая была прямой, то мы могли бы вычислить интеграл по формуле трапеций:
easyintegrate :: Fractionala =>(a ->a) ->a ->a ->a
easyintegrate f a b =(f a +f b) *(b -a) /2
Но мы хотим интегрировать не только прямые линии. Мы представим, что функция является ломаной
прямой линией. Мы посчитаем интеграл на каждом из участков и сложим ответы. При этом чем ближе точки
друг к другу, тем точнее можно представить функцию в виде ломаной прямой линии, тем точнее будет
значение интеграла.
Проблема в том, что мы не знаем заранее насколько близки должны быть точки друг к другу. Это зависит
от функции, которую мы хотим проинтегрировать. Но мы можем построить последовательность решений.
На каждом шаге мы будем приближать функцию ломаной прямой, и на каждом шаге число изломов будет
Читать дальше