Вы находитесь на странице: 1из 21

Лекция 9.

Примеры на ML: Символьное Дифференцирование

Введение в Функциональное программирование

Джон Харрисон

Университет Кембриджа

10 сентября 2008 г.

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Темы

Символьные вычисления
Представление данных
Система вывода
Дифференцирование
Упрощение посредством переписывания

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Символьные вычисления

Сейчас мы обсудим прикладные задачи, в которых проводятся


манипуляции над математическими выражениями, в общем случае
содержащими переменные.
Есть несколько удачных «систем компьютерной алгебры», таких как
Axiom, Maple и Mathematica, все они могут выполнять определённые
символьные операции, полезные в математической работе.
Примеры таких операций: разложение полиномов,
дифференцирование и интегрирование выражений.
Мы покажем как для подобных задач можно использовать ML.
Давайте рассмотрим задачу символьного дифференцирования.
Это позволит проиллюстрировать все типичные части систем
символьных вычислений: представление данных, внутренние
алгоритмы, разбор и вывод результатов.

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Представление данных
Мы будем рассматривать выражения, которые строятся из
переменных, констант и n-арных операторов.
Следовательно, мы определяем рекурсивный тип следующим
образом:
#type term = Var o f s t r i n g
| Const of s t r i n g
| Fn o f s t r i n g ∗ ( term l i s t ) ; ;
Type term d e f i n e d .

Например, выражение sin(x + y )/cos(x − exp(y )) − ln(1 + x)


представляется:
Fn ( "−" ,
[ Fn ( " / " , [ Fn ( " s i n " , [ Fn ( "+" , [ Var " x " ;
Var " y " ] ) ] ) ;
Fn ( " c o s " , [ Fn ( "−" , [ Var " x " ;
Fn ( " exp " ,
[ Var " y " ] ) ] ) ] ) ] ) ;
Fn ( " l n " , [ Fn ( "+" , [ C o n s t " 1 " ; Var " x " ] ) ] ) ] ) ; ;
Джон Харрисон Введение в Функциональное программирование
Лекция 9. Примеры на ML: Символьное Дифференцирование

Нам требуется разбор и печать

Чтение и запись выражений в сыром виде довольно неудобны. Это


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

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Вывод выражений

Как мы хотим печатать выражения?


Переменные и константы записываются по их именам.
Обычные n-арные функции, выводятся в виде f (x1 , . . . , xn ), т.е.
записывается имя функции и рядом, в скобках, список
аргументов.
Инфиксные бинарные операции, такие как + записываются
между своими аргументами.
Скобки используются по необходимости.
В целях сокращения использования скобок, для инфиксных
операторов вводится понятие приоритета.

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Сохранение приоритета
Мы можем завести список бинарных операторов с их приоритетами,
вроде следующего:
#l e t i n f i x e s =
[ "+" , 1 0 ; "−" , 1 0 ; " ∗" , 2 0 ; " / " , 2 0 ] ; ;

Списки такого вида, связывающие значения с ключами, называются


ассоциативными списками. Для того чтобы получить значение по
ключу, используется следующая функция:
#l e t r e c a s s o c a ( ( x , y ) : : r e s t ) =
i f a = x then y e l s e a s s o c a r e s t ; ;

В нашем случае, мы определяем:


#l e t g e t _ p r e c e d e n c e s = a s s o c s i n f i x e s ; ;

Этот алгоритм линейного поиска неэффективен, но прост и подходит


для простых примеров. Для неучебных приложений больше подойдёт
использование хеширования.
Джон Харрисон Введение в Функциональное программирование
Лекция 9. Примеры на ML: Символьное Дифференцирование

Делаем приоритеты изменяемыми


Из-за статического связывания, изменение списка инфиксных
операторов не меняет get_precedence.
Однако, мы можем сделать infixes ссылкой, а значит изменяемым:
#l e t i n f i x e s =
r e f [ "+" , 1 0 ; "−" , 1 0 ; "∗" , 2 0 ; " / " , 2 0 ] ; ;
...
#l e t g e t _ p r e c e d e n c e s =
assoc s (! i n f i x e s ) ; ;
g e t _ p r e c e d e n c e : s t r i n g −> i n t = <fun>
#g e t _ p r e c e d e n c e "^" ; ;
Uncaught e x c e p t i o n : M a t c h _ f a i l u r e
#i n f i x e s := ( "^" , 3 0 ) : : ( ! i n f i x e s ) ; ;
− : unit = ()
#g e t _ p r e c e d e n c e "^" ; ;
− : i n t = 30

Такое решение не является чисто функциональным, но, возможно,


более естественно.
Джон Харрисон Введение в Функциональное программирование
Лекция 9. Примеры на ML: Символьное Дифференцирование

Определение инфиксных операторов


Мы будем считать оператор инфиксным, только если он встречается
в списке infixes с приоритетом. Мы можем записать:
#l e t i s _ i n f i x s =
t r y get_precedence s ; true
with _ −> f a l s e ; ;

поскольку get_precedence не определена на неинфиксных


операторах, и, поэтому, выбросит исключение. Альтернативный
(и более правильный) способ использует вспомогательную функцию
can, которая проверяет успешность применения её первого
аргумента ко второму:
#l e t can f x =
try f x ; true
with _ −> f a l s e ; ;
can : ( ’ a −> ’ b ) −> ’ a −> b o o l = <fun>
#l e t i s _ i n f i x = can g e t _ p r e c e d e n c e ; ;
i s _ i n f i x : s t r i n g −> b o o l = <fun>

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Печать: обсуждение

Печать задаётся двумя взаимно рекурсивными функциями.


Функция string_of_term принимает два аргумента. Первый – это
«приоритетный оператор», а второй – терм.
Например, при печати правого аргумента из x * (y + z),
приоритетным оператором является * – произведение. При этом,
если второй аргумент является аппликацией инфиксного оператора
(в нашем случае +), и меньшего приоритета, то он окружается
скобками.
Вторая функция через запятую печатает список термов. Это нужно
для вывода списка аргументов функций вида f (x1 , . . . , xn ), n > 2.

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Печать: код
#l e t r e c s t r i n g _ o f _ t e r m p r e c =
fun ( Var s ) −> s
| ( C ons t c ) −> c
| ( Fn ( f , a r g s ) ) −>
i f l e n g t h a r g s = 2 & i s _ i n f i x f then
l e t prec ’ = get_precedence f in
l e t s1 = string_of_term prec ’
( hd a r g s )
and s 2 = s t r i n g _ o f _ t e r m p r e c ’
( hd ( t l a r g s ) ) i n
l e t s s = s 1^" ␣ "^ f ^" ␣ "^ s 2 i n
i f p r e c ’ <= p r e c then " ( "^ s s ^" ) " e l s e s s
else
f ^" ( " ^( s t r i n g _ o f _ t e r m s a r g s )^ " ) "
and s t r i n g _ o f _ t e r m s t =
match t with
[ ] −> ""
| [ t ] −> s t r i n g _ o f _ t e r m 0 t
| ( h : : t ) −> ( s t r i n g _ o f _ t e r m 0 h )^ " , "^
( string_of_terms t ) ; ;
Джон Харрисон Введение в Функциональное программирование
Лекция 9. Примеры на ML: Символьное Дифференцирование

Печать: установка

CAML Light поддерживает установку системы печати, задаваемой


пользователем.
После того, как система печати установлена пользователем,
выражения типа term будут печататься ею.
Для взаимодействия с интерпетатором мы используем некоторые
специальные функции из библиотеки format CAML Light.
##open " f o r m a t " ; ;
#l e t p r i n t _ t e r m s =
open_hvbox 0 ;
print_string
( " ‘ " ^( s t r i n g _ o f _ t e r m 0 s )^ " ‘ " ) ;
close_box ( ) ; ;
p r i n t _ t e r m : term −> u n i t = <fun>
#i n s t a l l _ p r i n t e r " p r i n t _ t e r m " ; ;
− : unit = ()

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Печать (до установки)

#t ; ;
t : term =
Fn
( "−" ,
[ Fn
( "/ " ,
[ Fn ( " s i n " , [ Fn ( "+" , [ Var " x " ; Var " y " ] ) ] ) ;
Fn ( " c o s " , [ Fn ( "−" , [ Var " x " ; Fn ( " exp " ,
[ Var " y " ] ) ] ) ] ) ] ) ;
Fn ( " l n " , [ Fn ( "+" , [ C o n s t " 1 " ; Var " x " ] ) ] ) ] )

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Печать (после установки)

#i n s t a l l _ p r i n t e r " p r i n t _ t e r m " ; ;
− : unit = ()
#t ; ;
t : term = ‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘
#l e t x = t ; ;
x : term = ‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘
#(x , t ) ; ;
− : term ∗ term =
‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘ ,
‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘
#[x ; t ; x ] ; ;
− : term l i s t =
[ ‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘ ;
‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘ ;
‘ s i n ( x + y ) / c o s ( x − exp ( y ) ) − l n ( 1 + x ) ‘ ]

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Дифференцирование: алгоритм

Есть хорошо известные (изучаемые в школе) правила


дифференцирования сложных выражений.
Если выражение – одна из элементарных функций одного
аргумента, например sin(x), то результат дифференцирования –
известная производная этой функции.
Если выражение представимо в форме f (x) + g (x), то по
правилу суммы, результат дифференцирования есть
f 0 (x) + g 0 (x). Аналогично для разности.
Если выражение задано в форме f (x) ∗ g (x), применяется
правило Лейбница, т.е. возвращается f 0 (x) ∗ g (x) + f (x) ∗ g 0 (x).
Если выражение является одной из композицией стандартных
функций: f (g (x)), то применяется правило последовательного
дифференцирования, результат которого: g 0 (x) ∗ f 0 (g (x)).
Эти правила легко переводятся в рекурсивные алгоритмы.

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Дифференцирование: код (начало)

l e t r e c d i f f e r e n t i a t e x tm = match tm with
Var y −> i f y = x then C o n s t " 1 " e l s e C o n s t " 0 "
| Co n s t c −> C o n s t " 0"
| Fn ( "−" , [ t ] ) −> Fn ( "−" , [ d i f f e r e n t i a t e x t ] )
| Fn ( "+" , [ t 1 ; t 2 ] ) −> Fn ( "+" , [ d i f f e r e n t i a t e x t 1 ;
d i f f e r e n t i a t e x t2 ] )
| Fn ( "−" , [ t 1 ; t 2 ] ) −> Fn ( "−" , [ d i f f e r e n t i a t e x t 1 ;
d i f f e r e n t i a t e x t2 ] )
| Fn ( "∗" , [ t 1 ; t 2 ] ) −>
Fn ( "+" , [ Fn ( " ∗" , [ d i f f e r e n t i a t e x t 1 ; t 2 ] ) ;
Fn ( "∗" , [ t 1 ; d i f f e r e n t i a t e x t 2 ] ) ] )
| Fn ( " i n v " , [ t ] ) −> c h a i n x t
( Fn ( "−" , [ Fn ( " i n v " , [ Fn ( "^" , [ t ; C o n s t " 2 " ] ) ] ) ] ) )

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Дифференцирование: код (конец)

| Fn ( "^" , [ t ; n ] ) −> c h a i n x t
( Fn ( " ∗" , [ n ;
Fn ( "^" , [ t ;
Fn ( "−" , [ n ; C o n s t " 1 " ] ) ] ) ] ) )
| Fn ( " exp " , [ t ] ) −> c h a i n x t tm
| Fn ( " l n " , [ t ] ) −> c h a i n x t ( Fn ( " i n v " , [ t ] ) )
| Fn ( " s i n " , [ t ] ) −> c h a i n x t ( Fn ( " c o s " , [ t ] ) )
| Fn ( " c o s " , [ t ] ) −> c h a i n x t
( Fn ( "−" , [ Fn ( " s i n " , [ t ] ) ] ) )
| Fn ( " / " , [ t 1 ; t 2 ] ) −> d i f f e r e n t i a t e x
( Fn ( " ∗" , [ t 1 ; Fn ( " i n v " , [ t 2 ] ) ] ) )
| Fn ( " t a n " , [ t ] ) −> d i f f e r e n t i a t e x
( Fn ( " / " , [ Fn ( " s i n " , [ t ] ) ; Fn ( " c o s " , [ t ] ) ] ) )
and c h a i n x t u = Fn ( "∗" , [ d i f f e r e n t i a t e x t ; u ] ) ; ;

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Примеры дифференцирования
Попробуем несколько примеров:
#l e t t 1 = Fn ( " s i n " , [ Fn ( "∗" , [ C o n s t " 2 " ;
Var " x " ] ) ] ) ; ;
t 1 : term = ‘ s i n ( 2 ∗ x ) ‘
#d i f f e r e n t i a t e " x " t 1 ; ;
− : term = ‘ ( 0 ∗ x + 2 ∗ 1 ) ∗ c o s ( 2 ∗ x ) ‘
#l e t t 2 = Fn ( " t a n " , [ Var " x " ] ) ; ;
t 2 : term = ‘ t a n ( x ) ‘
#d i f f e r e n t i a t e " x " t 2 ; ;
− : term =
‘(1 ∗ cos ( x )) ∗ inv ( cos ( x )) +
s i n ( x ) ∗ ( ( 1 ∗ −( s i n ( x ) ) ) ∗
−( i n v ( c o s ( x ) ^ 2 ) ) ) ‘
#d i f f e r e n t i a t e " y " t 2 ; ;
− : term =
‘(0 ∗ cos ( x )) ∗ inv ( cos ( x )) +
s i n ( x ) ∗ ( ( 0 ∗ −( s i n ( x ) ) ) ∗
−( i n v ( c o s ( x ) ^ 2 ) ) ) ‘

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Упрощение выражений

Кажется, всё хорошо работает, но при этом не делается некоторых


очевидных упрощений, таких как: 0 ∗ x = 0. Так получается частично
от того, что мы применяем рекурсивные правила, такие как правило
последовательного дифференцирования композиции, даже в
тривиальных случаях. Запишем функцию, выполняющую упрощение:

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Упрощение выражений: код


#l e t simp = fun
( Fn ( "+" , [ C o n s t " 0 " ; t ] ) ) −> t
| ( Fn ( "+" , [ t ; C o n s t " 0 " ] ) ) −> t
| ( Fn ( "−" , [ t ; C o n s t " 0 " ] ) ) −> t
| ( Fn ( "−" , [ C o n s t " 0 " ; t ] ) ) −> Fn ( "−" , [ t ] )
| ( Fn ( "+" , [ t 1 ; Fn ( "−" , [ t 2 ] ) ] ) ) −> Fn ( "−" , [ t 1 ; t 2 ] )
| ( Fn ( "∗" , [ C o n s t " 0 " ; t ] ) ) −> C o n s t "0 "
| ( Fn ( "∗" , [ t ; C o n s t " 0 " ] ) ) −> C o n s t "0 "
| ( Fn ( "∗" , [ C o n s t " 1 " ; t ] ) ) −> t
| ( Fn ( "∗" , [ t ; C o n s t " 1 " ] ) ) −> t
| ( Fn ( "∗" , [ Fn ( "−" , [ t 1 ] ) ; Fn ( "−" , [ t 2 ] ) ] ) ) −>
Fn ( "∗" , [ t 1 ; t 2 ] )
| ( Fn ( "∗" , [ Fn ( "−" , [ t 1 ] ) ; t 2 ] ) ) −>
Fn ( "−" , [ Fn ( " ∗" , [ t 1 ; t 2 ] ) ] )
| ( Fn ( "∗" , [ t 1 ; Fn ( "−" , [ t 2 ] ) ] ) ) −>
Fn ( "−" , [ Fn ( " ∗" , [ t 1 ; t 2 ] ) ] )
| ( Fn ( "−" , [ Fn ( "−" , [ t ] ) ] ) ) −> t
| t −> t ; ;

Джон Харрисон Введение в Функциональное программирование


Лекция 9. Примеры на ML: Символьное Дифференцирование

Примеры упрощения
Мы должны применить simp рекурсивно, по индукции:
#l e t r e c dsimp = fun
( Fn ( fn , a r g s ) ) −>
simp ( Fn ( fn , map dsimp a r g s ) )
| t −> simp t ; ;

Теперь мы получаем лучшие результаты:


#dsimp ( d i f f e r e n t i a t e " x " t 1 ) ; ;
− : term = ‘ 2 ∗ c o s ( 2 ∗ x ) ‘
#dsimp ( d i f f e r e n t i a t e " x " t 2 ) ; ;
− : term = ‘ c o s ( x ) ∗ i n v ( c o s ( x ) ) +
sin (x) ∗
( s i n ( x ) ∗ inv ( cos ( x ) ^ 2)) ‘
#dsimp ( d i f f e r e n t i a t e " y " t ) ; ;
− : term = ‘ 0 ‘

Есть и другие способы упрощения, например cos(x) ∗ inv (cos(x)) = 1.


Хорошее алгебраическое упрощение – это сложная задача!

Джон Харрисон Введение в Функциональное программирование

Вам также может понравиться