Я надеюсь написать серию статей про программирование на LISP и функциональное программирование вообще. Обещаю быть проще и ближе к народу, т.е. непрограммистам. Они и сами разберуться, а LISP программы больше всего нужны именно технарям, физикам и практикам. В чём я и попытаюсь вас убедить.
В первом посте по LISP-тематике я рассмотрю пример из отличной книги Х.Гулда и Я.Тобочника “Компьютерное моделирование в физике”. Начну с первого примера первой части - Задача об остывании кофе (с. 25).
Програмное содержание книги устарело, авторы используют диалект бейсика Turbo BASIC. Однако, программы вполне работоспособны на многих современных диалектах “классического” бейсика. Я же предлагаю переписать примеры программ на LISP, посмотреть, что получиться, а потом, может, и убедить вас в том, что получилось лучше, чем было.
В задаче рассматривается численное решение дифференциальных уравнений методом Эйлера. Того-же эффекта можно гораздо легче достичь в одной из многочисленных CAS (Computer Algebra System) системах компьютерной алгебры, вроде широко известного MathCAD или свободно распростаняемой MAXIMA (которая, кстати, основана на LISP). Чем плохо использование CAS я ещё расскажу, пока кратко поясню, что в них хорошо решать уравнения и строить графики, но для настоящего программирования они не приспособлены, хотя и имеют свои собственные языки программирования разной степени продвинутости.
С формулировкой задачи и математическим аппаратом предлагаю ознакомиться в самой книге, благо классическая физика с 1980-х ничуть не изменилась и в части описания математического аппарата и физических принципов книга всегда современна.
На с. 31 приводится полный листинг программы, приведу его на тот случай, если вас интересуют собственно вопросы программирования на LISP и его особенностей.
PROGRAM example
CALL initial(y, x, dx, n)
CALL Euler(y, x, dx, n)
END
SUB initial(y, x, dx, n)
LET x=1
LET xmax=1
LET y=1
LET dx=0.1
LET n=(xmax-x)/dx
END SUB
SUB Euler(y, x, dx, n)
FOR i=1 to n
LET slope=2*x ! Это собственно функция, которую мы вычисляем
LET change=slope*dx
LET y=y+change
LET x=x+dx
PRINT x,y
NEXT i
END SUB
Попытаемся отвлечься от наших навыков программирования и посмотреть на код если не взглядом младенца, то взгядом математика, который вообще не знает бейсика. Пожалуй, такой математик поймёт суть вычислений из формул. Но обратит внимание, что в коде определяются две переменные, change и slope, которые являются излишними. Очевидно, они введены для упрощения понимания кода. Очень существенно, что дифференцируемая функция находится в самом тексте программы, в строке LET slope=2*x
, раз уж у нас функция вида ( dy/dx = 2 \times x ). Значит, чтобы продифференцировать функцию другого вида, придётся править текст самой программы. Потом математик обратит внимание, что за инициацию вычисления отвечает специальный кусок кода, процедура initial (которой специально дано говорящее название), и там также определены переменные, которые передаются в функцию - процедуру Euler. Внутри процедуры легко опознать цикл (или, для математика, ряд) i - 1…n.
Вообщем, даже начинающий математик записал бы то же самое гораздо более лаконично, но помня, что каждая формула уменьшает число читателей вдвое я оставлю эту формулу математикам же. А сам приведу LISP-код.
(defun euler (y x dx xmax)
;;;; approximate solution of differential equations of euler method
(if (<= x xmax)
(euler (print (+ (* (* 2 x) dx) y)) (princ (+ x dx)) dx xmax)
y
)
)
(euler 1 1 0.1 2)
Легко увидеть, что строк программы стало гораздо меньше. Можно было бы сделать ещё меньше, текст остался бы синтаксически правильным, хотя и более трудным для чтения. На взгляд программиста код стал менее понятным, но этот пост не для них. Математик увидит тут рекурсию, и это куда ближе к его записи численного дифференцирования по методу Эйлера. Просто практичный человек, которому важен результат, обратит внимание на то, что запись стала весьма короткой, результат достигается, а к внешнему виду легко привыкнуть, если не выработалось привычки к императивному стилю программирования (вместо того, чтобы пояснять, что это такое, я лучше постепенно объясню, что такое функциональный стиль, чем он лучше и, самое главное, где он лучше).
На LISP можно было бы написать программу как две капли воды похожую на код на бейсике, но тогда мы не получили бы никаких преимуществ, а вот недостатки стиля LISP (все эти нагромождения скобок) проявились бы в полный рост. Помимо меньшего размера и большей выразительности преимущества таковы:
- не осталось лишних переменных, теперь их пять и все они необходимы;
- цикл FOR … NEXT заменился рекурсией, вы же заметили, что функция вызывает саму себя, но с новыми параметрами, чтобы вести вычисления;
- и самое главное - теперь можно дифференцировать любое уравнение первого порядка, вида dy/dt = g(x) (сделаем вид, что не заметили формулу) используя механизм лямбда-функций. А так как лямбда-функции требуют отдельного пояснения, я рассмотрю эту вариацию кода в следующем посте.
Скачать текст программы. Рекомендуется изменить расширение файла с txt на lsp.
Продолжение в следующей статье.