Оригинал доступен на cs.dartmouth.edu

Задание значения

Задание значения преобразовывает скалярную величину, такую как 2 в степенной ряд [2,0,0, …]. Это позволяет нам писать выражения типа 2*sins – где sins является степенным рядом – не нарушая требования Haskell о том, что оба операнда * должны принадлежать к одному типу данных.

Haskell неявно применяет к целочисленным константам функцию задания значения frominteger. Мы придаем frominteger значение, которое показывает, как преобразовывать целые числа в степенные ряды.

frominteger принадлежит классу типов Num наряду с обычными арифметическими операциями «+», «-» и «*». Мы помещаем степенные ряды, представленные в виде списков чисел, в указанный класс с помощью объявления, которое, в частности, гласит:

 instance (Num a, Eq a) => Num [a] где

     fromInteger c = series(fromInteger c)

Это помещает тип [a] – то есть списки с элементами типа a — в класс Num при условии, что сам тип a находится в классе Num.

Новый экземпляр fromInteger типа Num a=>Integer−>[a]определяется с помощью старого экземпляра типа Num a=>Integer−>a. Это обеспечивает определение c в типе, необходимом для элементов списка, обычно это Integer или Rational.

Отрицание

Стандартное наименование для унарного отрицания в Haskell – это negate, но оно может быть записано в выражениях как «−».

Умножение

Если F и G – это два степенных ряда с первыми членами f и g и последними F’ и G’, таким образом, что

F(x) = f + xF’(x)

G(x) = g + xG’(x),

то тогда их произведение будет

fg + x(F’)(g + xG’) + xfG’

что непосредственно преобразуется в

(f:ft) * gs@(g:gt) = f*g : ft*gs + series(f)*gt

Поскольку сигнатура (*) равна Num a=>a−>a−>a, мы не можем использовать (*) для умножения данных разного типа; отсюда и задание значения ряду. Если допускаются конечные ряды – как в практичном пакете, – то fG’ может быть реализовано как [f]*gt.

Подсказка. Более эффективной – менее явной – реализацией fG’ является map (f*) gt.

Деление

Если Q является частным рядов F и G, то у нас получится:

F = f+xF’ = QG = (q+xQ‘)(g+xG’)

откуда видно, что f = qg и F’ = (q+xQ‘)G’ + Qg = QG’ + Qg, и, наконец, что

Q = q+xQ‘ = f /g + x(1/g)(F’QG’)

Длинное деление вкратце!

Сложная функция

Сложная функция F(x) и G(x) равна F(G(x)), которая расширяется до

f + (g+xG’)F’(G)

Первым параметром F(G) является f + gF'(g), что является бесконечной суммой, если только F не является конечным рядом (то есть многочленом). Таким образом, для сохранности нам требуется g = 0. Формула сложной функции становится такой:

F(G(x)) = f + xG’F’(G)

Реверсия

Реверсия – это процесс вычисления композиционного обратного степенного ряда F(x).

Обратное R удовлетворяет F(R(x)) = x, которое расширяется до

f + (r+xR‘)(F’(R)) = x

Чтобы обратная функция F'(R) выполнялась, у нас должно быть r = 0, из чего следует, что f = 0.

Решение для R’ дает

R‘ = 1/F’(R)

Интеграция и дифференциация

Использование последовательности [1..], в выражениях

int fs = 0 : zipWith (/) fs [1..]

   diff (_:ft) = zipWith (*) ft [1..]

это хорошо, но не идеально, поскольку вводит класс Enum в контексты типа:

int :: (Fractional a, Enum a) => [a] −> [a]

   diff :: (Num a, Enum a) => [a] −> [a]

Enum в типе напоминает нам, что эти операции объединяют коэффициенты с индексами терминов, которые задают определенные арифметические последовательности, тем самым накладывая ограничения на Enum. Reminder жесткий; int pascal становится ошибкой типа, потому что коэффициенты в pascal являются списками в классе Num, но не в классе Enum. Проблема может быть решена переброской степенных рядов в класс Enum (так же довольно непринужденно, как Haskell проделывает это с рациональными числами) или путем отказа от записи арифметической последовательности. Первоначально выполнив последнее, практичный пакет теперь идет альтернативным путем: сопоставьте fromInteger со списком [1..], чтобы получить желаемый тип: Num a => [a].

Примеры

Проверка соответствия типов в формуле

tans = revert(int(1/(1:0:1)))

показывает, что (1:0:1) должен быть списком. Поскольку правый операнд (:) является списком, то последняя «1» должна быть списком; поэтому ей задается значение ряда. Мы можем заменить (1:0:1) на [1,0,1] только когда используем пакет степенных рядов, подстроенный под обработку конечных списков.

Подсказка. Данное выражение рационально, но tans = sins/coss проще и быстрее.

В определении pascal

1/[1, −[1,1]]

представляет 1/(1z0 − (1x0+1x1)z1).  Автоматическое задание значений преобразует его в неуклюжую форму:

[[1]]/[[1], −[1,1]]