Prelude Strict>
lazySafeHead []Nothing
188 | Глава 11: Ленивые чудеса
Отметим, что сопоставление с образцом в let
и where выражениях является ленивым. Функцию lazyHeadмы могли бы написать и так:
lazyHead a =
xwhere
(x:xs) = alazyHead a =
let
(x:xs) = ain
x
Посмотрим как используются ленивые образцы при построении потоков, или бесконечных списков. Мы
будем представлять функции одного аргумента потоками значений с одинаковым шагом. Так мы будем пред-
ставлять непрерывные функции дискретными сигналами. Считаем, что шаг дискретизации (или шаг между
соседними точками) нам известен.
Где
ренциальных уравнений вида:
Символ ˆ
Где
по всем точкам функции, превращённой в дискретный сигнал. Такой метод приближения дифференциаль-
ных уравнений называют методом Эйлера. Теперь мы можем выразить следующий элемент сигнала через
предыдущий.
Закодируем это уравнение:
-- шаг дискретизации
dt :: Fractional
a => adt =
1e-3-- метод Эйлера
int :: Fractional
a => a -> [a] -> [a]int x0 (f:
fs) = x0 : int (x0 + dt * f) fsСмотрите в функции int мы принимаем начальное значение x0 и поток всех значений функции пра-
вой части уравнения, поток значений функции
результата, а остальные значения получаем рекурсивно.
Определим две вспомогательные функции:
time :: Fractional
a => [a]time =
[0, dt .. ]dist :: Fractional
a => Int -> [a] -> [a] -> adist n a b =
( / fromIntegral n) $foldl’ (+
) 0 $ take n $ map abs $ zipWith (-) a bФункция time пробегает все значения отсчётов шага дискретизации по времени. Это тождественная функ-
ция представленная в виде потока с шагом dt.
Функция проверки результата dist принимает два потока и по ним считает расстояние между ними. Эта
функция говорит, что расстояние между двумя потоками в n первых точках равно сумме модулей разности
между значениями потоков. Для того чтобы оценить среднее расхождение, мы делим в конце результат на
число точек.
Также импортируем для удобства символьный синоним для fmap из модуля Control.Applicative
.Ленивее некуда | 189
import Control.Applicative
((<$> ))...
Проверим функцию int. Для этого сохраним все новые функции в модуле Stream.
hs. Загрузим модульв интерпретатор и вычислим производную какой-нибудь функции. Найдём решение для правой части кон-
станты и проверим, что у нас получилась тождественная функция:
*Stream>
dist 1000 time $ int 0 $ repeat 17.37188088351104e-17
Функции практически совпадают, порядок ошибки составляет 10
функций. Посмотрим, что будет если в правой части уравнения стоит тождественная функция:
*Stream>
dist 1000 ((\t -> t^2/2) <$> time) $ int 0 time2.497500000001403e-4
Решение этого уравнения равно функции
2
Есть функции, которые определяются рекурсивно в терминах дифференциальных уравнений, например
экспонента будет решением такого уравнения:
∫
0
Опишем это уравнение в Haskell:
e =
int 1 eНаше описание копирует исходное математическое определение. Добавим это уравнение в модуль Stream
и проверим результаты:
*Stream>
dist 1000 (map exp time) e^CInterrupted.
К сожалению вычисление зависло. Нажмём ctrl+
c и разберёмся почему. Для этого распишем вычислениепотока чисел e:
e
-- раскроем e
=>
int 1 e
-- раскроем int, во втором варгументе
-- int стоит декомпозиция,
=>
int 1 e@
(f:fs)-- для того чтобы узнать какое уравнение
-- для int выбрать нам нужно раскрыть
-- второй аргумент, узнать корневой
-- конструктор, раскроем второй аргумент:
=>
int 1 (int 1 e)
=>
int 1 (int 1e@
(f:fs))-- такая же ситуация
=>
int 1 (int 1 (int 1 e))
Проблема в том, что первый элемент решения мы знаем, мы передаём его первым аргументом и присо-
единяем к решению, но
проверить все аргументы, в которых есть декомпозиция. И он начинает проверять, но слишком рано. Нам
бы хотелось, чтобы он сначала присоединил к решению первый аргумент, а затем выполнял бы вычисления
следующего элемента.
C помощью ленивых образцов мы можем отложить декомпозицию второго аргумента на потом:
int :: Fractional
a => a -> [a] -> [a]int x0 ~
(f:fs) = x0 : int (x0 + dt * f) fsТеперь мы видим:
*Stream>
dist 1000 (map exp time) e4.988984990735441e-4
190 | Глава 11: Ленивые чудеса
Вычисления происходят. С помощью взаимно-рекурсивных функций мы можем определить функции си-
нус и косинус:
sinx =
int 0 cosxcosx =
int 1 (negate <$> sinx)