<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8641042312683198534</id><updated>2012-01-26T12:35:21.366+04:00</updated><category term='modeling'/><category term='lisp'/><category term='scala'/><category term='f#'/><category term='хаскель'/><category term='nemerle'/><category term='fp'/><category term='моделирование'/><category term='размышления'/><category term='haskell'/><category term='лисп'/><title type='text'>David Sorokin's WebLog in Russian</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>31</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-7786917638965731599</id><published>2012-01-26T11:51:00.000+04:00</published><updated>2012-01-26T11:51:49.059+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><title type='text'>А не пора ли разбить Scala Library на части?</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;br /&gt;Библиотека Scala времени исполнения (она же, Scala Library) выросла к версии 2.9.1 аж до восьми с половиной мегабайт с лишним от почти четырех в версии 2.7.7. Для распространения десктопных приложений на Scala это очень плохо. Конечно, есть ProGuard, который убирает все лишнее, но в него нет веры.&lt;br /&gt;&lt;br /&gt;Так, только что пропустил свой код через ProGuard и стал тестировать. Ужалось все до мегабайта, но на выходе из приложения получил неприятное NoSuchMethodException. Там создавался анонимный класс, и почему-то один его метод был безжалостно вырезан ProGuard'ом.&lt;br /&gt;&lt;br /&gt;Интересно, а собираются ли разбивать Scala Library на части по примеру того, как сделано в .NET? По-моему пора.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-7786917638965731599?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/7786917638965731599/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2012/01/scala-library.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7786917638965731599'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7786917638965731599'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2012/01/scala-library.html' title='А не пора ли разбить Scala Library на части?'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-4679803410988183539</id><published>2012-01-16T19:47:00.000+04:00</published><updated>2012-01-16T19:47:19.212+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><title type='text'>Глубоко-вложенные вычисления</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;br /&gt;Тема возникла из моих ответов на вопросы, заданные другим человеком на форумах. Речь пойдет о рекурсивных вычислениях, когда глубина вложенности выше предельно допустимой для стека вызовов. Я покажу, как такая задача может быть решена на Common Lisp. Сразу отмечу, что это возможно не всегда, но в большинстве случаев работает.&lt;br /&gt;&lt;br /&gt;В основе лежит та же самая идея, что используется в F# Async. Мы просто переводим наши рекурсивные функции на язык продолжений. &amp;nbsp;Пугаться здесь не стоит, сами мы этого делать не станем. За нас все самое сложное и рутинное сделают WITH-CALL/CC и его специальная версия для функций DEFUN/CC из пакета CL-CONT. Ниже везде предполагается, что пакет CL-CONT импортирован.&lt;br /&gt;&lt;br /&gt;Но прежде определимся с исходной задачей. Ниже приведены функции, которые вылетают со Stack Overflow.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun compute (x)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (cond ((zerop x) 0)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (t (+ (compute (1- x))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1))))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun run ()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (compute 10000000)) ;; Stack Overflow&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Функции намерено взяты такими. Сами по себе эти функции ничего не представляют. Нам интересно то, что функция COMPUTE рекурсивная, с большой глубиной, вызов - не хвостовой. В общем случае, рекурсивных вызовов может быть несколько.&lt;br /&gt;&lt;br /&gt;Перепишем функции через продолжения. Функцию COMPUTE определим через DEFUN/CC. Т.е. она будет возвращать не обычное значение, а вычисление, которое еще нужно запустить. В F# это примерно означало бы то, что функция COMPUTE возвращала бы некоторое значение типа Async&amp;lt;'a&amp;gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun/cc compute/cc (x)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (cond ((zerop x) 0)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (t (+ (compute/cc (1- x))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1))))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Таким образом мы должны преобразовать всякую функцию, вложенность которой может быть огромной. Вызывать их можно из DEFUN/CC и WITH-CALL/CC:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun run/cc ()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (with-call/cc (compute/cc 10000000)))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Если у вас SBCL, то на этом можно остановиться. У этой лисп-машины превосходный оптимизатор хвостового вызова. У нас фактически все операции COMPUTE превращаются в цепочку хвостовых вызовов. Поэтому все работает. Стек как бы уходит в память.&lt;br /&gt;&lt;br /&gt;Увы, не все лисп-машины хорошо оптимизируют хвостовые вызовы. Для CLozure CL и LispWorks Personal мы по-прежнему получим Stack Overflow. К счастью есть выход - использовать трамплин.&lt;br /&gt;&lt;br /&gt;Внутри вычисления DEFUN/CC нам доступно продолжение. Если глубина стека вызовов стала большой, то мы можем запомнить продолжение и раскрутить в обратную сторону стек, возвращая управление некоторому внешнему циклу. Внутри этого цикла мы будем проверять, а нет ли у нас очередного продолжения для, так сказать, продления вычисления. Если есть, то запускаем это продолжение. Фокус состоит в том, что при запуске продолжения стек вызовов уже очищен, что нам и требуется.&lt;br /&gt;&lt;br /&gt;Сначала определим утилиты трамплина:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defparameter *cont* nil)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun/cc trampoline-push/cc ()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (call/cc&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp;(lambda (k)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;(push k *cont*))))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defmacro trampoline/cc (expr)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (let ((result (gensym)))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; `(progn&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(trampoline-push/cc)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(let ((,result ,expr))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(trampoline-push/cc)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;,result))))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defmacro with-trampoline/cc (&amp;amp;body body)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (let ((result (gensym)))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; `(let ((,result nil))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(with-call/cc&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(trampoline-push/cc)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;,@body)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(loop while *cont*&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; do (let ((cont (pop *cont*)))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(setf ,result (funcall cont))))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;,result)))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Утилита TRAMPOLINE-PUSH/CC кладет продолжение вычисления в ячейку *CONT* и возвращает управление внешнему циклу из WITH-TRAMPOLINE/CC, откуда все должно быть запущено. Макрос TRAMPOLINE/СС оборачивает заданное выражение, где трамплин вызывается до и после вычисления выражения.&lt;br /&gt;&lt;br /&gt;Мы можем использовать трамплин часто, но это неэффективно. Пусть он вызывается на каждой сотой итерации:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(defun/cc smart-compute/cc (x)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (cond ((zerop x) 0)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ((zerop (mod x 100)) &amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;;; on every 100th iteration use the trampoline&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(+ (trampoline/cc (smart-compute/cc (1- x)))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (t&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(+ (smart-compute/cc (1- x))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1))))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;;; No Stack Overflow&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;(defun smart-run/cc ()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; (with-trampoline/cc (smart-compute/cc 10000000)))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Это работает даже для CLISP, где нет никакой оптимизации хвостового вызова. Мы успешно имитирует рекурсивный вызов с глубиной вложенности десять миллионов!&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-4679803410988183539?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/4679803410988183539/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2012/01/blog-post.html#comment-form' title='Комментарии: 6'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4679803410988183539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4679803410988183539'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2012/01/blog-post.html' title='Глубоко-вложенные вычисления'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-9176982851843603290</id><published>2012-01-14T20:56:00.001+04:00</published><updated>2012-01-14T20:59:21.667+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><title type='text'>Первые впечатления о CAPI из LispWorks</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;span lang="RU"&gt;Сегодня впервые использовал &lt;/span&gt;CAPI &lt;span lang="RU"&gt;для создания каркаса будущего редактора диаграмм. Остался очень довольным. Местами не похоже на &lt;/span&gt;Windows Forms&lt;span lang="RU"&gt;, &lt;/span&gt;WPF&lt;span lang="RU"&gt;/&lt;/span&gt;Silverlight&lt;span lang="RU"&gt;, &lt;/span&gt;Swing&lt;span lang="RU"&gt;, &lt;/span&gt;SWT&lt;span lang="RU"&gt;, но разобраться можно. Работает, что отрадно.&lt;/span&gt;&lt;br /&gt;&lt;div class="MsoNormal"&gt;&lt;span lang="RU"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="MsoNormal"&gt;&lt;span lang="RU"&gt;Еще очень радует среда &lt;/span&gt;LispWorks&lt;span lang="RU"&gt;.&amp;nbsp;&lt;/span&gt;Сейчас в проекте 250 килобайт кода на лиспе.&amp;nbsp;&lt;span lang="RU"&gt;Секунда или две после внесения изменений – и я уже вижу обновленное окошко моего редактора. Помню, как я мучился в ожидании, когда почти ту же самую задачу реализовывал на &lt;/span&gt;Scala &lt;span lang="RU"&gt;с помощью &lt;/span&gt;IntelliJ Idea&lt;span lang="RU"&gt;. Приходилось ждать целую вечность!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-9176982851843603290?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/9176982851843603290/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2012/01/capi-lispworks.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/9176982851843603290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/9176982851843603290'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2012/01/capi-lispworks.html' title='Первые впечатления о CAPI из LispWorks'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-2965026063810517095</id><published>2011-09-10T17:19:00.000+04:00</published><updated>2011-09-10T17:19:42.683+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Ускоряющий инлайнинг</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;Компилятор GHC достаточно хорошо оптимизирует, когда код свален в одну кучу в одном модуле. Исполняется быстро, но с таким кодом работать неудобно. Так в одной куче у меня код долго и оставался, пока сегодня не попробовал прагму компилятора INLINE, которая подобна конструкции inline в C++. &lt;br /&gt;&lt;br /&gt;Без этой прагмы после разбиения кода на модули, некоторые тесты стали отрабатывать медленнее в 3-4 раза на счетных задачах. Тогда я прогнал один из тестов через профайлер, и затем добавил прагму самой долгоисполняющейся функции. Я с самого начала догадывался, что эта за функции, а профайлер лишь подтвердил мое предположение. &lt;br /&gt;&lt;br /&gt;Так вот, чудесная новость состоит в том, что прежняя скорость вернулась, какой она была до разбиения программы на модули. Другими словами, некоторые вещи ускорились в 3-4 раза.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-2965026063810517095?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/2965026063810517095/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2011/09/blog-post.html#comment-form' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/2965026063810517095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/2965026063810517095'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2011/09/blog-post.html' title='Ускоряющий инлайнинг'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-8864006548119431016</id><published>2011-03-30T12:18:00.000+04:00</published><updated>2011-03-30T12:18:44.141+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='хаскель'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Портировал одну свою работу на хаскель</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;Портировал свою библиотеку имитационного моделирования Айвика с F# на хаскель. Зарелизил самую первую версию 0.1 на Hackage DB вместе с 51-страничным описанием в формате PDF.&lt;br /&gt;&lt;br /&gt;Умеет этот порт многое. По сравнению с версией для F# дизайн стал чище, а реализация тем более. Вначале даже пытался бороться с чистотой через unsafePerformIO, копируя один в один подход из F#, за что получил от оптимизирующего компилятора. В итоге понял, что чистота — это благо. Научился ее использовать.&lt;br /&gt;&lt;br /&gt;В общем, получил море позитива. Доволен результатом.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-8864006548119431016?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/8864006548119431016/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2011/03/blog-post_30.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8864006548119431016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8864006548119431016'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2011/03/blog-post_30.html' title='Портировал одну свою работу на хаскель'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-3767030852928981083</id><published>2011-03-04T10:58:00.000+03:00</published><updated>2011-03-04T10:58:04.538+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><title type='text'>Маниловщина</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', serif;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', serif;"&gt;Иногда очень хочется, чтобы F# был отделен от .NET. Чтобы программы на нем работали под юниксами и виндой без использования тяжелой виртуальной машины. Или, как минимум, все его новшества были портированы в окамл. Уж больно F# хорош!&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', serif;"&gt;Однако реализация F# иногда хромает. Например, в нем есть такие костыли как OptimizedClosures.FSharpFunc и FSharpFunc.InvokeFast. На деле это означает, что при вызове функции со многими аргументами задействуется RTTI, чтобы узнать, а есть ли прямой вызов функции, минуя каррирование. Во многом потому, что это снизу .NET. Подозреваю, что в окамле такой вызов функции сильно оптимизирован, и он не использует RTTI.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-3767030852928981083?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/3767030852928981083/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2011/03/blog-post.html#comment-form' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/3767030852928981083'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/3767030852928981083'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2011/03/blog-post.html' title='Маниловщина'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-5950475222338784923</id><published>2010-11-01T20:35:00.000+03:00</published><updated>2010-11-01T20:35:57.202+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Simtegra MapSys v4.0</title><content type='html'>Мы выпустили четвертую версию приложения &lt;a href="http://www.simtegra.com/"&gt;Simtegra MapSys&lt;/a&gt;. Это визуальная среда моделирования для системной динамики (имитационное моделирование). Программа создает иллюзию простоты для пользователя, хотя я понимаю, что внутри она устроена очень непросто. В этом ее ценность для меня как разработчика.&lt;br /&gt;&lt;br /&gt;Всего разработано мною более 240 тысяч строк кода на C# вместе с сопутствующими библиотеками. Пока еще можно двигаться вперед, используя Far и jEdit, изредка загружая Visual Studio C# Express для отладки. Подавляющая часть кода была создана при помощи замечательного редактора jEdit. А в последнее время стал все чаще использовать Far в качестве редактора... Для сборки сначала использовал NMAKE, потом перешел на MSBuild. Пока еще используем .NET v2.0 и WinForms.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-5950475222338784923?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/5950475222338784923/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/11/simtegra-mapsys-v40.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5950475222338784923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5950475222338784923'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/11/simtegra-mapsys-v40.html' title='Simtegra MapSys v4.0'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1452924097951642073</id><published>2010-10-06T12:15:00.000+04:00</published><updated>2010-10-06T12:15:50.320+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='размышления'/><title type='text'>О логике, математике и программировании</title><content type='html'>Навеяно одними однотипными высказываниями на форуме одного индивидуума.&lt;br /&gt;&lt;br /&gt;Человек не рождается с умением логически мыслить. Логика – это искусственное изобретение человеческого разума. Она противоестественна тому, как мы на самом деле мыслим. Даже в логических законах Аристотеля спустя почти две с половиной тысячи лет современными математиками была найдена ошибка. А если сам Аристотель ошибался, то что остается нам, простым смертным?&lt;br /&gt;&lt;br /&gt;Возьмем математику. Многие выдающиеся решения контр-интуитивны. Должно снизойти какое-то откровение свыше, должна быть необычайная интуиция, чтобы уметь решать сложные математические задачи. Места логике в чистом виде здесь нет. Мы – не машины. Вот там – действительно непробиваемая логика. И эти машины, кстати, мыслить не умеют. Хаос – атрибут разума, того самого разума, который породил логику и машины.&lt;br /&gt;&lt;br /&gt;А программирование в этом вопросе мало отличается от математики, только масштабы сильно скромнее.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1452924097951642073?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1452924097951642073/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/10/blog-post.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1452924097951642073'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1452924097951642073'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/10/blog-post.html' title='О логике, математике и программировании'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-4472718970051463660</id><published>2010-09-18T11:00:00.003+04:00</published><updated>2010-09-18T11:15:29.846+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Размышления о движке моделирования</title><content type='html'>&lt;div&gt;Я часто думаю на эту тему. О некоторых вещах уже писал. Попытаюсь собрать все воедино, чтобы потом, уже по прошествии некоторого времени, сам смог бы перечитать. Пишу для себя. Надеюсь, что в никакие агрегаторы и планеты не попадет.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Создал два очень разных движка: MapSim и Aivika.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Первый – MapSim (лицензия LGPL).  Генерирует очень быструю симуляцию, даже удивительно быструю. Но умеет только системную динамику. Очень узкая область. Движок очень хорошо делает свое дело, хотя даже в таком качестве MapSim может показаться отступнически революционным, ибо целиком написан на C#, а не на популярных в этой области Си или Си++ с Фортраном.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. Второй – Айвика (лицензия BSD). Фантастически универсальный движок. Умеет самые разные парадигмы имитационного моделирования. Но редкостный тормоз на задачах системной динамики (интегралы чудовищно неэффективны). Дискретное событийное моделирование более-менее (быстрый двоичный хип). Скорость же процесс-ориентированной дискретной симуляции сильно зависит от качества реализации системы памяти (создается много легковесных функций) и хвостовой рекурсии (.NET на порядок быстрее, чем Mono), но в целом медленная симуляция. Агентное моделирование медленнее, чем в AnyLogic, но я думаю, что приемлемо. Еще движок Айвика – это полный переворот сознания для обычного модельера. Трудно для понимания.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;В общем, два антипода. Есть идея скрестить их. Это возможно благодаря универсальности Айвики. Тут два момента.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Симуляцию, сгенерированную с помощью MapSim, можно представить как вычисление в монаде моделирования Dynamics. Это значит, что такую симуляцию можно использовать в Айвике как равноправную. Есть накладные расходы, связанные с оборачиванием итерационного процесса, порождаемого MapSim, в небольшую прослойку, которая будет выглядеть внешне как динамический процесс Dynamics. Нужно будет где-то хранить значения выходных переменных. Чем меньше переменных на выходе, тем быстрее эта прослойка. Придется повозиться.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. Внутри симуляции под управлением MapSim можно использовать вычисления в монаде Dynamics. Другими словами, MapSim может использовать модели Айвики. Накладные расходы минимальны. Реализовать очень просто.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Теоретических проблем нет. Все должно работать. Я уже продумал. И если бы я снова стал создавать визуальную среду моделирования Simtegra MapSys, то сейчас уже взял бы за основной общесистемный движок Айвику, а системную динамику оптимизировал бы с помощью MapSim. То есть, я очень серьезно отношусь к Айвике, хотя с первого взгляда она может показаться просто игрушкой функционального программирования :)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-4472718970051463660?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/4472718970051463660/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/09/blog-post_18.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4472718970051463660'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4472718970051463660'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/09/blog-post_18.html' title='Размышления о движке моделирования'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-7163750447179761502</id><published>2010-09-17T15:34:00.002+04:00</published><updated>2010-09-17T15:39:37.673+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><title type='text'>CodeDom для F#</title><content type='html'>&lt;div&gt;Сегодня взял свою библиотеку MapSim, которая умеет создавать код через CodeDom, и заменил СSharpCodeProvider на FSharpCodeProvider из F# PowerPack. Сработало! MapSim по заданному входу сгенерировал код на F#. Только местами забавный код получился. В одном месте создаются исключения только для того, чтобы их тут же перехватить. Странно как-то. Но в целом качество кода сопоставимо с тем, что у меня создает стандартный провайдер C#.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-7163750447179761502?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/7163750447179761502/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/09/codedom-f.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7163750447179761502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7163750447179761502'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/09/codedom-f.html' title='CodeDom для F#'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-5307259245067845048</id><published>2010-09-15T12:48:00.001+04:00</published><updated>2010-09-15T12:51:35.933+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>А не переписать ли MapSim на F#?</title><content type='html'>&lt;div&gt;Иногда возникают мысли переписать свою библиотеку моделирования MapSim на F#. Сейчас она написана на C#. Думаю, что будет большая польза от использования абстрактных типов данных (ADTs). Также можно будет добавить разный back-end, т.е. создавать симуляции не только для .NET, но и для Java, JavaScript и C++ с фортраном. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Чертовски быстрая симуляция получается даже сейчас на .NET. Код линеен, почти не создает объектов, не выделяет память, а используемая память умещается в кеш процессора. JIT-компилятор создает код, сравнимый с ассемблером. Симуляцию можно еще ускорить, если использовать локальные переменные – сейчас везде используются поля объекта для надежности (в .NET есть лимит в 64 кб для локальных переменных).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Думаю, что когда-нибудь можно будет подружить MapSim и Айвику. Тогда симуляция в Айвике для системной динамики будет намного быстрее. Эту тему затронул в обзорной статье Aivika Overview.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-5307259245067845048?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/5307259245067845048/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/09/mapsim-f.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5307259245067845048'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5307259245067845048'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/09/mapsim-f.html' title='А не переписать ли MapSim на F#?'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-8195554997073240938</id><published>2010-09-15T12:30:00.001+04:00</published><updated>2010-09-15T12:33:55.103+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Aivika версии 2.0.12.0</title><content type='html'>&lt;div&gt;Залил новую версию своей библиотеки по моделированию. Сам код не изменился. Перевел проект на .NET v4 и Visual Studio 2010. Добавил статью Aivika Overview, где на восьми страницах дается обзор метода. Кроме прочего, упоминаются и монады, ибо вся соль моего метода в том, что заданная имитационная модель сводится к динамическому процессу, а такой процесс является монадой. Отсюда фантастические возможности по комбинированию процессов. Хотя я уже писал об этом прежде :)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-8195554997073240938?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/8195554997073240938/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/09/aivika-20120.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8195554997073240938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8195554997073240938'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/09/aivika-20120.html' title='Aivika версии 2.0.12.0'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-2348427458154278646</id><published>2010-09-12T13:57:00.002+04:00</published><updated>2010-09-12T14:07:47.013+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><title type='text'>О вычислительных выражениях</title><content type='html'>Уже почти год пишу на F#, и мне так понравились вычислительные выражения! Удивительно простая в использовании штука. Такое выражение можно записать в императивном стиле как почти обычный код на F#, используя while, for и try, а оно потом на выходе будет преобразовано компилятором F# в выражение, состоящее из композиции функции. Императивная конструкция превращается в сугубо функциональное выражение. Замечательная идея. Сюда же легко вписываются монады и моноиды. Вот, с монадными трансформерами накладка. &lt;div&gt;&lt;br /&gt;Поддержка таких выражений – это просто непередаваемая по значимости вещь для императивных монад типа Async для асинхронных вычислений. Более того, не нужно поддерживать на уровне языка продолжения – для них можно использовать те же вычислительные выражения (но нужна поддержка TCO на уровне исполняющей среды). Продолжения не совсем императивны, но все же блоки try-with и try-finally обрабатывать в таком языке как F# надо. И здесь нет известной проблемы Common Lisp.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Кстати, о Лиспе. Считаю, что вычислительные выражения и макросы мешают друг другу.  Эти выражения предполагают, что есть ограниченное множество конструкций языка, которые могут быть “офункционалены” (превращены в функции). Макросы это нарушают. Когда я добавлял поддержку вычислительных выражений в Немерле, то оставлял макросы как есть. Не знаю, можно ли придумать здесь что-то лучше? (интересно, а как макросы и продолжения сосуществуют в Схеме?)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Мне теперь стало очень не хватать вычислительных выражений в Скале и Окамле. Скаловский for-comprehension смотрится как-то слабо, особенно для императивных монад, когда вычисления нужно разбавить обычным кодом. Что касается Окамла, то для него есть одно расширение, но очень хотелось бы, чтобы решение было стандартизировано и “из-коробки”. Надеюсь, что когда-нибудь добавят.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-2348427458154278646?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/2348427458154278646/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/09/blog-post.html#comment-form' title='Комментарии: 7'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/2348427458154278646'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/2348427458154278646'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/09/blog-post.html' title='О вычислительных выражениях'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1622464491162012714</id><published>2010-08-13T19:10:00.004+04:00</published><updated>2010-08-13T21:37:31.996+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><title type='text'>"Асинхронные" продолжения</title><content type='html'>Сегодня осознал одну замечательную вещь. Оказывается, что в F# можно использовать async workflow вместо продолжений… Фактически, async состоит из трех продолжений: основной ветки вычислений, обработки ошибок и экстренной отмены вычислений. Все это разные продолжения под куполом единого значения в монаде Async.&lt;br /&gt;&lt;br /&gt;Вот, пример примитивного вычисления функции Аккермана с кешированием промежуточных результатов:&lt;br /&gt;&lt;pre  style="background:#ffffff;color:#000000;"&gt;&lt;span style=" font-weight:bold; color:#800000;"&gt;open&lt;/span&gt; System&lt;span style=" ;color:#008c00;"&gt;.&lt;/span&gt;Collections&lt;span style=" ;color:#008c00;"&gt;.&lt;/span&gt;Generic&lt;br /&gt;&lt;br /&gt;&lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt; ackermann m n &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt;&lt;br /&gt;   &lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt; dict &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; Dictionary&lt;span style=" ;color:#808030;"&gt;&amp;lt;&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;_&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;_&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;&gt;&lt;/span&gt; &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt;&lt;br /&gt;   &lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt; fix m n x &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; async &lt;span style=" ;color:#808030;"&gt;{&lt;/span&gt;&lt;br /&gt;       &lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;!&lt;/span&gt; a &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; x&lt;br /&gt;       dict&lt;span style=" ;color:#008c00;"&gt;.&lt;/span&gt;Add &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;m&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; n&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; a&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt;&lt;br /&gt;       return&lt;span style=" ;color:#808030;"&gt;!&lt;/span&gt; x&lt;br /&gt;   &lt;span style=" ;color:#808030;"&gt;}&lt;/span&gt;&lt;br /&gt;   &lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt; &lt;span style=" font-weight:bold; color:#800000;"&gt;rec&lt;/span&gt; ack m n &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; async &lt;span style=" ;color:#808030;"&gt;{&lt;/span&gt;&lt;br /&gt;       &lt;span style=" font-weight:bold; color:#800000;"&gt;if&lt;/span&gt; m &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;0&lt;/span&gt; &lt;span style=" font-weight:bold; color:#800000;"&gt;then&lt;/span&gt;&lt;br /&gt;           return n &lt;span style=" ;color:#808030;"&gt;+&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1&lt;/span&gt;&lt;br /&gt;       elif dict&lt;span style=" ;color:#008c00;"&gt;.&lt;/span&gt;ContainsKey &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;m&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; n&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt; &lt;span style=" font-weight:bold; color:#800000;"&gt;then&lt;/span&gt;&lt;br /&gt;           return dict.&lt;span style=" ;color:#808030;"&gt;[&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;m&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; n&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;]&lt;/span&gt;&lt;br /&gt;       elif n &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;0&lt;/span&gt; &lt;span style=" font-weight:bold; color:#800000;"&gt;then&lt;/span&gt;&lt;br /&gt;           return&lt;span style=" ;color:#808030;"&gt;!&lt;/span&gt; fix m n &lt;span style=" ;color:#808030;"&gt;&amp;lt;&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;|&lt;/span&gt; ack &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;m &lt;span style=" ;color:#808030;"&gt;-&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1&lt;/span&gt;&lt;br /&gt;       &lt;span style=" font-weight:bold; color:#800000;"&gt;else&lt;/span&gt;&lt;br /&gt;           &lt;span style=" font-weight:bold; color:#800000;"&gt;let&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;!&lt;/span&gt; x &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; ack m &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;n &lt;span style=" ;color:#808030;"&gt;-&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt;&lt;br /&gt;           return&lt;span style=" ;color:#808030;"&gt;!&lt;/span&gt; fix m n &lt;span style=" ;color:#808030;"&gt;&amp;lt;&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;|&lt;/span&gt; ack &lt;span style=" ;color:#808030;"&gt;(&lt;/span&gt;m &lt;span style=" ;color:#808030;"&gt;-&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;)&lt;/span&gt; x&lt;br /&gt;   &lt;span style=" ;color:#808030;"&gt;}&lt;/span&gt;&lt;br /&gt;   ack m n&lt;br /&gt;&lt;/pre&gt;А это результат работы:&lt;br /&gt;&lt;pre  style="background:#ffffff;color:#000000;"&gt;&lt;span style=" ;color:#808030;"&gt;&gt;&lt;/span&gt; ackermann &lt;span style=" ;color:#008c00;"&gt;3&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;19&lt;/span&gt; &lt;span style=" ;color:#808030;"&gt;|&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;&gt;&lt;/span&gt; Async&lt;span style=" ;color:#008c00;"&gt;.&lt;/span&gt;RunSynchronously&lt;span style=" ;color:#808030;"&gt;;&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;;&lt;/span&gt;&lt;br /&gt;Real&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;00&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;01&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;15&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;.849&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; CPU&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;00&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;01&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;15&lt;/span&gt;&lt;span style=" ;color:#008c00;"&gt;.816&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; GC gen0&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;1398&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; gen1&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;515&lt;/span&gt;&lt;span style=" ;color:#808030;"&gt;,&lt;/span&gt; gen2&lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;7&lt;/span&gt;&lt;br /&gt;&lt;span style=" font-weight:bold; color:#800000;"&gt;val&lt;/span&gt; it &lt;span style=" ;color:#808030;"&gt;:&lt;/span&gt; &lt;span style=" font-weight:bold; color:#800000;"&gt;int&lt;/span&gt; &lt;span style=" ;color:#808030;"&gt;=&lt;/span&gt; &lt;span style=" ;color:#008c00;"&gt;4194301&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Без продолжений был бы явный StackOverflowException при гораздо меньших значениях n и m.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1622464491162012714?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1622464491162012714/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/08/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1622464491162012714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1622464491162012714'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/08/blog-post.html' title='&quot;Асинхронные&quot; продолжения'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-6314740098989665577</id><published>2010-08-07T15:33:00.003+04:00</published><updated>2010-08-07T16:20:58.781+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><title type='text'>Разбор XML на F# с помощью комбинаторов</title><content type='html'>Возникла задача разбора потока данных XML, выдаваемых стандартным дотнетовским парсером XmlReader. Раньше я много раз это проделывал на C#, но часто приходилось создавать много дополнительных методов для обработки вложенных тегов XML. Код неприлично разрастался. Теперь захотелось выразительности и декларативности, чтобы разбор сводился к заданию структуры тегов с вкраплениями кода для сбора данных. Получилась представленная ниже крошечная библиотека, идейно близкая к парсер-комбинаторам.&lt;br /&gt;&lt;br /&gt;Итак, входной поток задается объектом-значением XmlReader. Неприятность в том, что этот объект несет в себе изменяемое состояние. Это ограничивает наши возможности по разбору. Вероятно, об откатах придется забыть.&lt;br /&gt;&lt;br /&gt;Представим наш парсер в виде функции, которая по заданному объекту XmlReader возвращает прочитанные данные в случае успеха, либо сигнализирует о неудаче.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;namespace Maritegra.Xml&lt;br /&gt;&lt;br /&gt;open System.Xml&lt;br /&gt;&lt;br /&gt;type XmlReader&lt;'a&gt; = XmlReader -&gt; 'a option&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Нужно поставить сразу условие. Если мы изменяем состояние объекта XmlReader, то тогда должны вернуть Some, т.е. успех. Наоборот, если функция парсера возвращает None, то тогда гарантируется, что состояние XmlReader не претерпело изменений. Следование этому условию делает разбор надежным, но заметно ограничивает наши возможности. Справедливый обмен.&lt;br /&gt;&lt;br /&gt;В определении полиморфного типа XmlReader&lt;’a&gt; использована такая особенность дотнета, которая разделяет одноименные типы по параметрам. То есть, введенный тип XmlReader&lt;’a&gt; и стандартный тип XmlReader из System.Xml – это два разных типа. Далее полиморфный тип XmlReader&lt;’a&gt; я буду называть парсером, а, говоря о стандартном типе XmlReader, буду просто использовать его название.&lt;br /&gt;&lt;br /&gt;Если сравнивать с другими парсерами, то введенный отличается тем, что аргумент функции, т.е. объект XmlReader, имеет состояние, а потому возвращать как результат его ненужно. Отсюда же возникает наложенное выше условие. В большинстве случаев мы не можем просто так прикоснуться к объекту XmlReader, не изменив его.&lt;br /&gt;&lt;br /&gt;Ближе к делу. Очевидно, что парсер XmlReader&lt;’a&gt; является монадой. Сильно не буду углубляться в эту тему. Лишь приведу необходимые определения.&lt;br /&gt;&lt;br /&gt;Вот, основные монадические функции, скрытые внутри отдельного модуля (новый F# обычно не позволяет определять глобальные функции вне модулей):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;module XmlReaderImpl =&lt;br /&gt;&lt;br /&gt;    let returnXR a = fun (r: XmlReader) -&gt; Some a&lt;br /&gt;    let bindXR m k = fun (r: XmlReader) -&gt; match m r with | Some a -&gt; (k a) r | None -&gt; None&lt;br /&gt;    let delayXR k  = fun (r: XmlReader) -&gt; (k ()) r&lt;br /&gt;    let zeroXR     = fun (r: XmlReader) -&gt; Some ()&lt;br /&gt;    let combineXR m1 m2 = fun (r: XmlReader) -&gt; match m1 r with | Some () -&gt; m2 r | None -&gt; None&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Теперь для монады нужен построитель, тип которого задается очень просто:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;open XmlReaderImpl&lt;br /&gt;&lt;br /&gt;type XmlReaderBuilder () =&lt;br /&gt;&lt;br /&gt;    member x.Return (a)  = returnXR a&lt;br /&gt;    member x.Bind (m, k) = bindXR m k&lt;br /&gt;    member x.Delay (k)   = delayXR (k: unit -&gt; XmlReader&lt;'a&gt;)&lt;br /&gt;    member x.Zero ()     = zeroXR&lt;br /&gt;    member x.Combine (m1, m2) = combineXR m1 m2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Завершающее определение построителя монады xmlreader:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[&amp;lt;AutoOpen&amp;gt;]&lt;br /&gt;module XmlReaderWorkflow =&lt;br /&gt;&lt;br /&gt;    let xmlreader = XmlReaderBuilder ()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Атрибут AutoOpen автоматически делает доступным определение построителя.&lt;br /&gt;&lt;br /&gt;Сейчас создадим еще один XmlReader, но уже модуль. Язык F# превосходно справляется с таким смешением имен, но для этого модуль нужно снабдить следующим длинным атрибутом CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix). Поместим в этот модуль функции запуска run и отображения map. В комментариях указана сигнатура функций. Кроме того, модуль снабдим атрибутом RequireQualifiedAccess, который указывает на обязательность имени модуля при обращении к функциям.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[&amp;lt;RequireQualifiedAccess&amp;gt;]&lt;br /&gt;[&amp;lt;CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)&amp;gt;]&lt;br /&gt;module XmlReader =&lt;br /&gt;&lt;br /&gt;    // val run: XmlReader -&gt; XmlReader&lt;'a&gt; -&gt; 'a&lt;br /&gt;    let run (r: XmlReader) m = &lt;br /&gt;        match m r with&lt;br /&gt;        | Some a -&gt; a&lt;br /&gt;        | None -&gt; failwithf "Invalid input data"&lt;br /&gt;&lt;br /&gt;    // val map: ('a -&gt; 'b) -&gt; XmlReader&lt;'a&gt; -&gt; XmlReader&lt;'b&gt;&lt;br /&gt;    let map f m = fun (r: XmlReader) -&gt;&lt;br /&gt;        match m r with&lt;br /&gt;        | Some a -&gt; Some (f a)&lt;br /&gt;        | None -&gt; None&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Все это следует из самого определения монады. До функций разбора дело пока не дошло. Они приведены ниже. Я также сделаю их частью определения модуля XmlReader. Поэтому код можно копировать с сохранением порядка и отступов.&lt;br /&gt;&lt;br /&gt;Начнем с простых комбинаторов, которые извлекают информацию об атрибутах. Они безболезненны и не меняют состояние объекта XmlReader. По заданному имени или порядковому номеру получаем значение атрибута, обернутое в монаду:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    // val attr: string -&gt; XmlReader&amp;lt;string&amp;gt;&lt;br /&gt;    let attr (name: string) = fun (r: XmlReader) -&gt; Some &lt;| r.GetAttribute (name)&lt;br /&gt;&lt;br /&gt;    // val attri: int -&gt; XmlReader&amp;lt;string&amp;gt;&lt;br /&gt;    let attri (i: int) = fun (r: XmlReader) -&gt; Some &lt;| r.GetAttribute (i)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Для извлечения текста можно использовать следующий простой комбинатор, который уже меняет состояние XmlReader:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    // val text: XmlReader&amp;lt;string&amp;gt;&lt;br /&gt;    let text = fun (r: XmlReader) -&gt; Some &lt;| r.ReadElementContentAsString ()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Более сложный случай – это разбор элемента. Комбинатор elem разбирает элемент с заданным именем, применяя для содержимого другой парсер. Если элемент не тот, то, не меняя состояния XmlReader, сразу возвращается None, как и было оговорено в изначальном условии, накладываемом на парсер.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    // val elem: string -&gt; XmlReader&lt;'a&gt; -&gt; XmlReader&lt;'a&gt;&lt;br /&gt;    let elem (name: string) m = fun (r: XmlReader) -&gt;&lt;br /&gt;        if r.IsStartElement (name) then m r else None&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Теперь нужны комбинаторы для разбора внутренних тегов. Таких комбинаторов два. Первый рассчитывает на императивную обработку содержимого – он является основным. Второй же больше соответствует функциональной парадигме. Устроены оба комбинатора одинаково.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    // val contents: XmlReader&amp;lt;unit&amp;gt; -&gt; XmlReader&amp;lt;unit&amp;gt;&lt;br /&gt;    let contents (m: XmlReader&amp;lt;unit&amp;gt;) = fun (r: XmlReader) -&gt;&lt;br /&gt;        if r.IsStartElement () then&lt;br /&gt;            if not r.IsEmptyElement then&lt;br /&gt;                let depth = r.Depth&lt;br /&gt;                while r.Read () &amp;&amp; (r.Depth &gt; depth) do&lt;br /&gt;                    if (r.Depth = 1 + depth) then &lt;br /&gt;                        m r |&gt; ignore&lt;br /&gt;            Some ()&lt;br /&gt;        else&lt;br /&gt;            None&lt;br /&gt;            &lt;br /&gt;    // val selectContents: XmlReader&lt;'a&gt; -&gt; XmlReader&lt;'a list&gt;&lt;br /&gt;    let selectContents (m: XmlReader&lt;_&gt;) = fun (r: XmlReader) -&gt;&lt;br /&gt;        if r.IsStartElement () then&lt;br /&gt;            [ if not r.IsEmptyElement then&lt;br /&gt;                let depth = r.Depth&lt;br /&gt;                while r.Read () &amp;&amp; (r.Depth &gt; depth) do&lt;br /&gt;                if (r.Depth = 1 + depth) then&lt;br /&gt;                    match m r with&lt;br /&gt;                    | Some a -&gt; yield a&lt;br /&gt;                    | None -&gt; ()&lt;br /&gt;            ] |&gt; Some&lt;br /&gt;        else None&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Комбинатор contents применяет заданный парсер ко всем внутренним тегам, чья глубина вложенности меньше на единицу. Второй комбинатор делает то же самое, но при этом извлекает полезную информацию.&lt;br /&gt;&lt;br /&gt;На этом определение модуля XmlReader закончено. Теперь в отдельном модуле введем вспомогательный бинарный оператор (+++), который бы объединял два заданных парсера, пытаясь сначала применить первый, а затем второй в случае неудачи.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;module Operators =&lt;br /&gt;&lt;br /&gt;    // val (+++): XmlReader&lt;'a&gt; -&gt; XmlReader&lt;'a&gt; -&gt; XmlReader&lt;'a&gt;&lt;br /&gt;    let (+++) m1 m2 = fun (r: XmlReader) -&gt;&lt;br /&gt;        match m1 r with&lt;br /&gt;        | Some a as n -&gt; n&lt;br /&gt;        | None -&gt; m2 r&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Библиотека закончена. Она содержится в пространстве имен Maritegra.Xml. &lt;br /&gt;&lt;br /&gt;Перейдем к скриптованию и тестированию. В режиме скрипта мы уже можем определять глобальные функции и данные. В качестве исходных данных возьмем следующий кусок XML:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;let document = &lt;br /&gt;    @"&amp;lt;struct&amp;gt;&lt;br /&gt;        &amp;lt;items&amp;gt;&lt;br /&gt;           &amp;lt;item id='1001' x='1'&amp;gt;10&amp;lt;/item&amp;gt;&lt;br /&gt;           &amp;lt;item id='1002' x='2'&amp;gt;20&amp;lt;/item&amp;gt;&lt;br /&gt;           &amp;lt;item id='1003' x='3'&amp;gt;30&amp;lt;/item&amp;gt;&lt;br /&gt;        &amp;lt;/items&amp;gt;&lt;br /&gt;        &amp;lt;links&amp;gt;&lt;br /&gt;           &amp;lt;link source-id='1001' target-id='1003'&amp;gt;PuperLink&amp;lt;/link&amp;gt;&lt;br /&gt;           &amp;lt;link source-id='1001' target-id='1002'&amp;gt;WeakLink&amp;lt;/link&amp;gt;&lt;br /&gt;        &amp;lt;/links&amp;gt;&lt;br /&gt;      &amp;lt;/struct&amp;gt;"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Задача состоит в извлечении информации из тегов item и link. Сделать это довольно просто:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;open System&lt;br /&gt;open System.Collections&lt;br /&gt;open System.Collections.Generic&lt;br /&gt;open System.IO&lt;br /&gt;open System.Xml&lt;br /&gt;&lt;br /&gt;open Maritegra.Xml&lt;br /&gt;open Maritegra.Xml.Operators&lt;br /&gt;&lt;br /&gt;type Item = Item of string * string * string&lt;br /&gt;type Link = Link of string * string * string&lt;br /&gt;&lt;br /&gt;let proc document =&lt;br /&gt;&lt;br /&gt;    use reader = new XmlTextReader (new StringReader (document))&lt;br /&gt;    &lt;br /&gt;    let items = List&lt;_&gt; ()&lt;br /&gt;    let links = List&lt;_&gt; ()&lt;br /&gt;    &lt;br /&gt;    (XmlReader.elem "struct" &lt;|&lt;br /&gt;        (XmlReader.contents &lt;|&lt;br /&gt;            (XmlReader.elem "items" &lt;|&lt;br /&gt;                (XmlReader.contents &lt;|&lt;br /&gt;                    (XmlReader.elem "item" &lt;|&lt;br /&gt;                        xmlreader {&lt;br /&gt;                            let! id   = XmlReader.attr "id"&lt;br /&gt;                            let! x    = XmlReader.attr "x"&lt;br /&gt;                            let! text = XmlReader.text&lt;br /&gt;                            items.Add (Item (id, x, text))&lt;br /&gt;                        })))&lt;br /&gt;            +++&lt;br /&gt;            (XmlReader.elem "links" &lt;|&lt;br /&gt;                (XmlReader.contents &lt;|&lt;br /&gt;                    (XmlReader.elem "link" &lt;|&lt;br /&gt;                        xmlreader {&lt;br /&gt;                            let! sourceId = XmlReader.attr "source-id"&lt;br /&gt;                            let! targetId = XmlReader.attr "target-id"&lt;br /&gt;                            let! text = XmlReader.text&lt;br /&gt;                            links.Add (Link (sourceId, targetId, text))&lt;br /&gt;                        })))))&lt;br /&gt;    |&gt; XmlReader.run reader&lt;br /&gt;&lt;br /&gt;    (items, links)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Теперь сюда помещаем определение значения document (единственное место, где порядок приведения кода отличается от линейного). Затем производим разбор и выводим результат:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;let (items, links) = proc document&lt;br /&gt;&lt;br /&gt;let sprintItem (Item (id, x, text)) = sprintf "Item (%s, %s, %s)" id x text&lt;br /&gt;let sprintLink (Link (sid, tid, text)) = sprintf "Link (%s, %s, %s)" sid tid text&lt;br /&gt;&lt;br /&gt;items |&gt; Seq.map sprintItem&lt;br /&gt;      |&gt; Seq.reduce (fun s1 s2 -&gt; s1 + ", " + s2)&lt;br /&gt;      |&gt; printfn "items = seq [%s]"&lt;br /&gt;&lt;br /&gt;links |&gt; Seq.map sprintLink&lt;br /&gt;      |&gt; Seq.reduce (fun s1 s2 -&gt; s1 + ", " + s2)&lt;br /&gt;      |&gt; printfn "links = seq [%s]"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Итак, удалось довольно рутинный разбор XML втиснуть внутрь одной функции. На мой взгляд, получилось выразительно, хотя и несколько пестрит словом XmlReader. Используя вышеприведенный подход, можно разбирать довольно сложный XML с большой степенью вложенности, оставаясь в рамках одной функции или выражения. Конечно, метод не претендует на полноту. Например, пока не придумал, как разбирать текстовые поля, чередующиеся с тегами на одном уровне, но мне это и ненужно. Тем не менее, метод интересен тем, что XmlReader очень быстр и легковесен в отличие от того же XmlDocument. Правда стоит отметить, что для XmlDocument можно использовать такую чудесную вещь как активное сопоставление с образцом, что позволяет писать чрезвычайно наглядный и выразительный код, но там нужно предварительно полностью загрузить документ XML в промежуточное представление XmlDocument. В общем, нет в мире совершенства!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-6314740098989665577?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/6314740098989665577/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/08/xml-f.html#comment-form' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/6314740098989665577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/6314740098989665577'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/08/xml-f.html' title='Разбор XML на F# с помощью комбинаторов'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1489487005076673897</id><published>2010-06-14T12:39:00.000+04:00</published><updated>2010-06-14T12:40:56.740+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='хаскель'/><title type='text'>Разбиение на модули</title><content type='html'>&lt;span lang="RU" style="font-size:11.0pt;line-height: 115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:RU;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;Я просто обалдеваю. Разбил программу на модули, а она стала в 4,3 раза медленнее. Пришлось&lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%; font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin;mso-fareast-font-family: Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font:minor-latin; mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:EN-US;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt; &lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;; mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font: minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:RU;mso-fareast-language:EN-US; mso-bidi-language:AR-SA"&gt;вернуть&lt;/span&gt;&lt;span style="font-size:11.0pt; line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:EN-US;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;. &lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;; mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font: minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:RU;mso-fareast-language:EN-US; mso-bidi-language:AR-SA"&gt;Компилятор&lt;/span&gt;&lt;span style="font-size:11.0pt; line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:EN-US;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;: The Glorious Glasgow Haskell Compilation System, version 6.12.1.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1489487005076673897?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1489487005076673897/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/06/blog-post_14.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1489487005076673897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1489487005076673897'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/06/blog-post_14.html' title='Разбиение на модули'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1006661327315826014</id><published>2010-06-06T13:43:00.002+04:00</published><updated>2010-06-06T13:54:04.489+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Дискретно-событийное моделирование</title><content type='html'>&lt;div&gt;Для своей маленькой библиотеки имитационного моделирования &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Айвика&lt;/a&gt; [http://sourceforge.net/projects/aivika/] в документацию добавил законченную главу о дискретно-событийном моделировании (DES) и интеграции подмоделей DES в единую гибридную модель. Там получилось все очень функционально.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Вообще, я часто думаю, а можно ли перевести библиотеку на Хаскель? Тем более, самый первый прототип был именно на Хаскеле. Но тут два препятствия. Во-первых, нужна мемоизация, которая бы действовала, пока симуляция запущена. Ну, это, мне кажется, можно обойти, ради скорости сфальшивив в некоторых местах через &lt;i&gt;unsafePerformIO&lt;/i&gt;. Все равно, все будет спрятано за монадами, что даст определенные непробиваемые гарантии (как сейчас на F# в Айвике).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Второе препятствие более сложное и принципиальное. Имитационные модели почти все стохастические, т.е. недетерминированные. И я пока не знаю, как это эффективно реализовать, да так, чтобы система типов осталась простой и ясной. В F# с этим проблем нет.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1006661327315826014?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1006661327315826014/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/06/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1006661327315826014'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1006661327315826014'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/06/blog-post.html' title='Дискретно-событийное моделирование'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-854699491551009087</id><published>2010-05-31T18:23:00.002+04:00</published><updated>2010-05-31T18:27:22.323+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Обновленная документация к Айвике</title><content type='html'>&lt;div&gt;Если кому интересно, то я сегодня выложил обновленную версию свой библиотеки моделирования &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Айвика&lt;/a&gt; [http://sourceforge.net/projects/aivika/]. Там содержится более полная документация. Я описал то, как реализуются в Айвике activity-oriented и event-oriented парадигмы дискретно-событийного моделирования (DES). Также есть краткая информация о монаде DynamicsCont, которая лежит в основе моей реализации process-oriented парадигмы DES. В общем, среди парадигм теперь осталось осветить этот самый process-oriented DES и агентное моделирование (agent-based modeling).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Удивительное дело получается. Сплав методов функционального программирования и имитационного моделирования. Наиболее поразительны результаты в области DES. Тут и две монады, одна из которых является монадным трансформером, параметризованным по другой монаде. Тут еще computation expressions, которые являются в F# синтаксическим сахаром для монад и моноидов. Они делают сочинение моделей DES довольно приятным занятием. Тут же замыкания, которые используются для передачи данных вместе с событиями. Само событие у меня – это просто значение (), обернутое в монаду Dynamics. В общем, красота.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Что несколько удручает, так это большая нагрузка на систему управления памятью (GC). Монады означают, что создается во время моделирования до черта кратко-живущих функций с замыканиями (модуль системной динамики этим не страдает). Но дотнетовский GC неплох, очень неплох. Его двойник в Mono оказался на одной модельке намного тормознее.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Мне все же кажется, что придуманная мною единая схема имитационного моделирования имеет право на жизнь. Можно по-быстрому запрототипировать довольно сложную гибридную модель. Так что, быть!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-854699491551009087?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/854699491551009087/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/05/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/854699491551009087'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/854699491551009087'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/05/blog-post.html' title='Обновленная документация к Айвике'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-8853948095010761736</id><published>2010-05-23T18:28:00.006+04:00</published><updated>2010-05-23T18:51:09.939+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Aivika версии 2.0</title><content type='html'>&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;p class="MsoNormal"&gt;В ходе разработки моей небольшой библиотеки моделирования &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Айвика&lt;/a&gt; на языке F# произошел ряд важных прорывных событий. Во-первых, я значительно ускорил модуль системной динамики. Обсчет модели по методу Рунге-Кутта стал в раз 5 быстрее. Потом я добавил агентное моделирование (АМ), как его понял после прочтения статьи Андрея Борщева (одного из создателей системы AnyLogic). Для этого я переписал очередь событий модуля дискретно-событийного моделирования (DES), который стал на порядки производительнее. В общем, теперь охватываются все три основные парадигмы имитационного моделирования (ИМ), и в целом я доволен скоростью имитации. Также создал каталог с примерами моделей, каждая из которых снабжена справочной информацией и ссылками. Теперь более подробно.&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;b&gt;Модуль системной динамики (SD)&lt;/b&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Мне удалось значительно ускорить этот модуль за счет отказа от функций нескольких аргументов. После компиляции такие функции вызываются в конечном итоге через метод InvokeFast, который вопреки своему названию совершенно не быстр. Там используется RTTI, что делает каждый такой вызов достаточно медленным. Если же функция имеет всего один аргумент, то тогда вызывается напрямую абстрактный метод Invoke, минуя RTTI. Вызов Invoke очень эффективен.&lt;/p&gt;&lt;p class="MsoNormal"&gt;Поэтому я переписал внутренности модуля так, чтобы везде использовалась функция одного аргумента. Теперь монада Dynamics стала просто частным случаем монады Reader с небольшим исключением: некоторые функции создают жестко контролируемый побочный эффект. Мне кажется, это можно как-то записать и в хаскеле, но там придется программировать на Си, реализуя местами функциональность монады ST.&lt;/p&gt;&lt;p class="MsoNormal"&gt;В итоге модуль системной динамики стал где-то в пять раз быстрее прежнего. Учитывая, что все в Айвике в конечном счете построено на монаде Dynamics, это имеет колоссальные последствия для всей библиотеки в целом.&lt;/p&gt;&lt;p class="MsoNormal"&gt;Еще я лучше осознал смысл термина комбинатор. Оказывается, что все функции eDSL системной динамики являются у меня комбинаторами. Причем выглядят они почти так же как в специализированных системах Simtegra MapSys, ithink или Vensim. Но у меня функции, которые принимают одни функции и создают другие, обладая свойством замыкания.&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;b&gt;Агентное моделирование (АМ)&lt;/b&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Система AnyLogic использует карты состояний (statecharts) для описания агентов. Я придумал API, которое позволяет достаточно декларативно задавать агенты и их состояния так, как если бы мы использовали эти самые карты состояний. Получается кратко и наглядно. Все действия задаются в монаде Dynamics. Широко используются такие возможности F# как взаимно-рекурсивные определения через let rec и объектные выражения (object expressions). Благодаря монаде Dynamics агенты легко интегрируются с модулем системной динамики.&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;b&gt;Дискретно-событийное моделирование (DES)&lt;/b&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Агентное моделирование реализовано поверх очереди событий, которая является сердцем DES.&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span class="Apple-style-span" style="font-family: Georgia, serif; color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://2.bp.blogspot.com/_RZXTBoE6nVc/S_k_FVlliGI/AAAAAAAAAAU/h45Vm2lNY5M/s400/Hierarchy.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5474476182972237922" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 346px; height: 400px; " /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;В предыдущей версии очередь была реализована как proof-of-concept, т.е. через двусторонний список… Тормоза стали особенно заметны после добавления АМ. Сначала я переписал очередь через иммутабельный хип, описанный в одной замечательной книге по хаскелю. Но функции добавления и удаления по-прежнему продолжали висеть в списке десяти самых тормозных функций, показываемых профайлером SharpDevelop. Тогда я переписал очередь через императивный бинарный хип, реализованный на основе массива. Функции работы c очередью ушли из списка. Агентное моделирование просто взлетело.&lt;/div&gt;&lt;p class="MsoNormal"&gt;&lt;b&gt;Примеры моделей&lt;/b&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Я создал аналоги моделей, используемых в системах Simtegra MapSys (SD), Berkeley&amp;amp;Madonna (SD), AnyLogic (АМ) и SimPy (DES). Также реализовал пару моделей, описанных в книге Ильи Труба “Объектно-ориентированное моделирование на C++”.&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;b&gt;Планы&lt;/b&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Считаю, что библиотека Айвика находится в достаточно зрелом состоянии. Например, ее можно использовать для создания визуализированных моделей, проигрываемых поверх Silverlight.&lt;/p&gt;&lt;p class="MsoNormal"&gt;Сейчас в документации достаточно полно освещен модуль системной динамики. Теперь нужно описать модули дискретно-событийного моделирования (DES) и агентного моделирования (АМ). Думаю, что можно заняться продвижением. Важно, что библиотеку можно использовать и из C#, но наиболее полно возможности доступны только через F#.&lt;/p&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-8853948095010761736?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/8853948095010761736/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/05/aivika-20.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8853948095010761736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8853948095010761736'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/05/aivika-20.html' title='Aivika версии 2.0'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_RZXTBoE6nVc/S_k_FVlliGI/AAAAAAAAAAU/h45Vm2lNY5M/s72-c/Hierarchy.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-5018653064365592013</id><published>2010-05-01T20:39:00.002+04:00</published><updated>2010-05-01T20:42:51.409+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Релиз MapSim версии 4.1</title><content type='html'>&lt;span lang="RU" style="font-size:11.0pt;line-height: 115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:RU;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;Сегодня выложил релиз версии 4.1 движка моделирования &lt;/span&gt;&lt;span style="font-size: 11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font: minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin; mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:EN-US;mso-fareast-language: EN-US;mso-bidi-language:AR-SA"&gt;&lt;a href="http://sourceforge.net/projects/mapsim/"&gt;MapSim&lt;/a&gt;&lt;/span&gt;&lt;span style="font-size:11.0pt; line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:RU;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt; &lt;span lang="RU"&gt;для системной динамики. Он используется в визуальной среде для моделирования Simtegra &lt;a href="http://simtegra.com/"&gt;MapSys&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;; mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font: minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:RU;mso-fareast-language:EN-US; mso-bidi-language:AR-SA"&gt;, также разработанном мною в сотрудничестве со своим партнером. Основная новая возможность – можно менять параметры во время симуляции. Это нужно для проигрывания симуляций в &lt;/span&gt;&lt;span style="font-size:11.0pt; line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:EN-US;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;MapSys&lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;; mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font: minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:RU;mso-fareast-language:EN-US; mso-bidi-language:AR-SA"&gt;. Все написано на &lt;/span&gt;&lt;span style="font-size:11.0pt; line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;;mso-ascii-theme-font:minor-latin; mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-hansi-theme-font: minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;;mso-bidi-theme-font:minor-bidi; mso-ansi-language:EN-US;mso-fareast-language:EN-US;mso-bidi-language:AR-SA"&gt;C&lt;/span&gt;&lt;span lang="RU" style="font-size:11.0pt;line-height:115%;font-family:&amp;quot;Calibri&amp;quot;,&amp;quot;sans-serif&amp;quot;; mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font: minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:&amp;quot;Times New Roman&amp;quot;; mso-bidi-theme-font:minor-bidi;mso-ansi-language:RU;mso-fareast-language:EN-US; mso-bidi-language:AR-SA"&gt;#.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-5018653064365592013?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/5018653064365592013/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/05/mapsim-41.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5018653064365592013'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5018653064365592013'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/05/mapsim-41.html' title='Релиз MapSim версии 4.1'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-9184780191836730441</id><published>2010-05-01T09:36:00.000+04:00</published><updated>2010-05-01T09:45:06.023+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Работа над ошибками</title><content type='html'>&lt;div&gt;Выпустил новую версию 1.0.4.0 библиотеки моделирования &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Aivika&lt;/a&gt; на F#. В прежней версии обнаружил ошибку в реализации блоков try-finally и try-with для вычислительного выражения dynamicscont. По своей сути это частный случай монадного трансформера, построенного на основе продолжения. Дело в том, что фактически надо хранить два продолжения, а не одно. Первое продолжение используется для основной ветки вычислений. Второе продолжение - для обработки исключительных ситуаций. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Обновленный тип выглядит так:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;type DynamicsCont&amp;lt;'a&amp;gt; = Dynamics&amp;lt;('a -&amp;gt; unit) * (exn -&amp;gt; unit)&amp;gt; -&amp;gt; Dynamics&amp;lt;unit&amp;gt;&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Выводится из следующего типа Cont с учетом одного упрощения, характерного именно для монадного типа Dynamics:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;type Cont&amp;lt;'a&amp;gt; = ('a -&amp;gt; unit) * (exn -&amp;gt; unit) -&amp;gt; unit&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Здесь функция от двух продолжений: основного потока вычислений и обработчика ошибок. Особенность заключается в том, что монадический переход bind должен быть защищен в узком месте блоком try. В случае возникновения ошибки должно активироваться второе продолжение для обработки ошибок.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Сначала я составил необходимые функции для Cont. Потом сверил с реализацией стандартного async. Убедился, что все правильно. Затем перенес на тип DynamicsCont. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Если есть интерес, то я мог бы написать эти функции для Cont. В интернете такой информации не видел. Там обычно рассматривается упрощенный случай из одного продолжения, не учитывающий обработку блоков try.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-9184780191836730441?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/9184780191836730441/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/04/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/9184780191836730441'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/9184780191836730441'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/04/blog-post.html' title='Работа над ошибками'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-519545612905344178</id><published>2010-04-25T20:08:00.000+04:00</published><updated>2010-04-25T20:12:41.538+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='nemerle'/><title type='text'>Добавил Computation Expressions в Nemerle</title><content type='html'>При содействии Андрея Белякова (WolfHound) и Владислава Чистякова (VladD2) я добавил поддержку computation expressions в Nemerle. Поддерживаются все вычислительные конструкции F#, но несколько своим образом, подстроенным под особенности самого Nemerle. Но пока синтаксис не устоялся. &lt;br /&gt;&lt;br /&gt;Я думаю, что мне удалось угадать алгоритм разбора правильно. На базе computation expressions реализованы list comprehension, array comprehension и enumerable (sequence) comprehension как частный случай общего механизма. Добавил все, используя макросы и не меняя сам компилятор языка. Получилась внешняя библиотека.&lt;br /&gt;&lt;br /&gt;Должен сказать, что Nemerle произвел очень приятное впечатление. Язык и среда достойны всяческого внимания.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-519545612905344178?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/519545612905344178/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/04/computation-expressions-nemerle.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/519545612905344178'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/519545612905344178'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/04/computation-expressions-nemerle.html' title='Добавил Computation Expressions в Nemerle'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-8472131963251076810</id><published>2010-04-14T11:40:00.000+04:00</published><updated>2010-04-14T11:47:13.329+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Aivika версии 1.0.3.0</title><content type='html'>&lt;div&gt;Выложил новую версию библиотеки моделирования &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Aivika&lt;/a&gt;. Написана она на F# с использованием двух монад. Эта библиотека позволяет создавать и запускать имитацию гибридных моделей. Сейчас охватывается системная динамика (System Dynamics) и Discrete Event Simulation (как time-driven, event-driven так и process-driven simulation). В планах добавление agent-based modeling. В тестах обгоняет SimPy примерно в два раза на его же типе задач, хотя Aivika умеет значительно больше. Есть недописанный обрывок документации. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Сейчас не буду вдаваться в маркетинговые лозунги. Напишу лишь пару технических деталей. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;В основе лежит (императивная) монада Dynamics, которая умеет системную динамику, т.е. запускать имитацию и интегрировать динамическую систему. Что характерно, дифф.-уравнения задаются в высшей степени декларативно. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Поверх этой монады есть ее продолжение DynamicsCont&amp;lt;’a&amp;gt; = Dynamics&amp;lt;’a -&amp;gt; unit&amp;gt; -&amp;gt; Dynamics&amp;lt;unit&amp;gt;. Фишка в том, что используя синтаксис вычислительный выражений, можно писать как бы “обычный” код на F# в монаде DynamicsCont, который будет временами прерываться. Это нужно для реализации “процессов”, которые могут усыпляться, просыпаться, ставиться в очередь на выполнение в такое-то время, блокироваться для захвата разделяемого ресурса и т.д. Все это предполагается в process-driven simulation. Используются свойства продолжения.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Так вот, такой код, используя лифт, может вызывать вычисления в исходной монаде Dynamics, т.е. интегрироваться с динамическими системами. Частный случай монадного трансформера. Это позволяет строить гибридный модели, где одна часть может быть описана дифф.урами, а другая задаваться дискретными процессами. В перспективе сюда добавлю агенты –  вся инфраструктура готова.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Все хозяйство представляет собой embedded domain specific language (eDSL). Поскольку внутри используются монады, то легко встраивать вызовы функций .NET внутрь симуляции через вычислительные выражения, т.е. через монадический bind. eDSL же дает возможность встраивать симуляции в обычные .NET приложения. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Подозреваю, что подобные системы стоят дорого. Проблема в том, что мой код получился очень маленьким по объему (90 кб)… и не таким быстрым.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-8472131963251076810?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/8472131963251076810/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/04/aivika-1030.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8472131963251076810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8472131963251076810'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/04/aivika-1030.html' title='Aivika версии 1.0.3.0'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1303203619028229581</id><published>2010-03-23T19:31:00.000+03:00</published><updated>2010-03-23T19:38:52.836+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Обратные связи</title><content type='html'>Язык F# в наследство от OCaml получил интересную возможность задавать рекуррентные отношения декларативно, как есть. Например, в моих сообщениях постоянно используется одна и та же система дифф-уров. Теперь ее можно переписать на F# следующим образом:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;nbsp; let rec a = integF (lazy (- ka*a)) 100.0&lt;br /&gt;&amp;nbsp; and b = integF (lazy (ka*a - kb*b)) 0.0&lt;br /&gt;&amp;nbsp; and c  = integF (lazy (kb*b)) 0.0&lt;br /&gt;&amp;nbsp; and ka = 1.0&lt;br /&gt;&amp;nbsp; and kb = 1.0&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Это – работающий пример, где функция &lt;code&gt;integF&lt;/code&gt; возвращает интеграл (как некое вычисление в монаде моделирования) по заданной производной и начальному значению. Здесь мы видим, что (1) переменные можно задавать произвольно без указания зависимости, (2) обратные связи задаются через явную ленивость. Фактически есть еще третий очень важный пункт: (3) компилятор сам следит за разрешимостью системы (в отличие от того же Haskell). Мне особенно нравится этот пункт, поскольку он делает подобный метод пригодным для широкого практического применения. Например, у нас имеется eDSL, а неискушенные пользователи задают свои системы. Компилятор сам все проверит и укажет на ошибку в случае необходимости. Неоценимое свойство.&lt;br /&gt;&lt;br /&gt;Правда, в случае одной более сложной системы компилятор F# (v1.9.9.9) почему-то неправильно вывел переменные. Соответствующий bug report был отослан. Я думаю, что это – временное явление.&lt;br /&gt;&lt;br /&gt;Рекуррентные отношения можно задавать и более хитрым способом, используя генераторы массивов:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;nbsp; let smoothNI (x: Lazy&amp;lt;Dynamics&amp;lt;float&amp;gt;&amp;gt;) (t: Lazy&amp;lt;Dynamics&amp;lt;float&amp;gt;&amp;gt;) n (i: Dynamics&amp;lt;float&amp;gt;) =&lt;br /&gt;&amp;nbsp;&amp;nbsp; let rec s = [|&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; for k = 0 to n-1 do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if k = 0 then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; yield integ (lazy ((x.Value - s.[k]) / (t.Value / (float n)))) i&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; yield integ (lazy ((s.[k-1] - s.[k]) / (t.Value / (float n)))) i |]&lt;br /&gt;&amp;nbsp;&amp;nbsp; in s.[n-1]&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Здесь возвращается функция (значение в монаде моделирования), которая называется &lt;i&gt;экспоненциальной порядка n сглаживающей x по времени t с начальным значением i&lt;/i&gt;. &lt;br /&gt;&lt;br /&gt;Мне нравится.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1303203619028229581?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1303203619028229581/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/03/blog-post.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1303203619028229581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1303203619028229581'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/03/blog-post.html' title='Обратные связи'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-4283401943692482548</id><published>2010-02-28T20:26:00.000+03:00</published><updated>2010-02-28T20:49:54.996+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='fp'/><category scheme='http://www.blogger.com/atom/ns#' term='лисп'/><title type='text'>Потоки (Streams)</title><content type='html'>&lt;dl&gt;  &lt;dt&gt; &lt;dl&gt;  &lt;dt&gt;  &lt;p class="Standard"&gt;Вчера прочитал главу SICP, посвященную потокам (streams). Возникло острое желание переписать примеры на Common Lisp (CL). Тема очень интересна сама по себе. Еще подстегивало то, что некоторые активисты упорно пропагандируют идею оторванности CL от функциональной парадигмы (ФП), как бы это абсурдно ни звучало. Но когда я приступил к переписыванию кода на CL, мое первое обманчивое впечатление было таким, что потоки будет реализовать труднее, чем в Схеме. Например, в CL нет аналога схемовского DEFINE, который бы позволил определять переменные рекурсивно. Но, к счастью, все разрешилось удачным образом. Как и должно было быть, выручили макросы, могучая сила CL. Они же мне позволили в свое время добавить очень простой и удобный синтаксический сахар для монад по типу нотации do, о чем я писал ранее.&lt;/p&gt;  &lt;p class="Standard"&gt;Итак, все начинается с ленивости. Для этого определяются конструкции DELAY и FORCE, но прежде нужна вспомогательная функция MEMO, которая возвращает функцию без аргументов, результат которой кешируется:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun memo (fun)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(let ((already-run? nil)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(result nil))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;#'(lambda ()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(if (not already-run?)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;(progn&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;              &lt;/span&gt;(setf result (funcall fun))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;              &lt;/span&gt;(setf already-run? t)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;              &lt;/span&gt;result)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;result))))&lt;/p&gt;  &lt;p class="Standard"&gt;Тут все по книге SICP, только там функция называлась &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MEMO-PROC&lt;/span&gt;.&lt;/p&gt;  &lt;p class="Standard"&gt;Дальше определяем конструкции DELAY и FORCE, причем DELAY должен быть непременно макросом, чтобы он мог упрятать свой аргумент в лямбду, сделав его тем самым ленивым:&lt;/p&gt;  &lt;p class="Standard"&gt;(defmacro delay (exp)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;`(memo #'(lambda() ,exp)))&lt;/p&gt;  &lt;p class="Code"&gt;(defun force (delayed-exp)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(funcall delayed-exp))&lt;/p&gt;  &lt;p class="Code"&gt;Теперь можно приступить к реализации примитивов, с помощью которых будут создаваться потоки. Среди этих примитивов выделяется особо конструктор пары CONS-STREAM, который тоже должен быть макросом, чтобы иметь возможность сделать свой второй аргумент ленивым. Собственно, в этом заключена суть потоков.&lt;/p&gt;  &lt;p class="Standard"&gt;(defmacro cons-stream (a b)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;`(cons ,a (delay ,b)))&lt;/p&gt;  &lt;p class="Code"&gt;(defun stream-car (stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(car stream))&lt;/p&gt;  &lt;p class="Code"&gt;(defun stream-cdr (stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(force (cdr stream)))&lt;/p&gt;  &lt;p class="Code"&gt;(defun stream-null (stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(null stream))&lt;/p&gt;  &lt;p class="Code"&gt;(defparameter *empty-stream* nil)&lt;/p&gt;  &lt;p class="Code"&gt;Функция STREAM-CDR раскрывает ленивую CDR-часть потока, если она не была до того уже раскрыта. Как помним, на нижнем уровне за это отвечает функция MEMO, которая встраивается при создании каждой пары. В общем, это все уже описано в SICP. Поэтому останавливаться не буду.&lt;/p&gt;  &lt;p class="Standard"&gt;Следующая функция STREAM-REF аналогична AREF, но работает уже с потоками.&lt;/p&gt;  &lt;p class="Standard"&gt;(defun stream-ref (stream n)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(loop for i from 0 to n&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;for s = stream then (stream-cdr s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;finally (return (stream-car s))))&lt;/p&gt;  &lt;p class="Standard"&gt;Несмотря на присутствие в реализации совсем нефункционального LOOP, функция является чистой.&lt;/p&gt;  &lt;p class="Standard"&gt;Далее идут отображения для потоков. Вполне в духе ФП.&lt;/p&gt;  &lt;p class="Standard"&gt;(defun stream-map (fun stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(if (stream-null stream) stream&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;(cons-stream (funcall fun (stream-car stream))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                   &lt;/span&gt;(stream-map fun (stream-cdr stream)))))&lt;/p&gt;  &lt;p class="Code"&gt;(defun stream-map2 (fun s1 s2)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(cond&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((stream-null s1) s1)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((stream-null s2) s2)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(t (cons-stream (funcall fun (stream-car s1) (stream-car s2))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;(stream-map2 fun (stream-cdr s1) (stream-cdr s2))))))&lt;/p&gt;  &lt;p class="Code"&gt;Далее понадобится функция фильтрации потока. Я поменял имя STREAM-FILTER на более идиоматическое. К тому же задействовал LOOP. При этом функция остается чистой.&lt;/p&gt;  &lt;p class="Standard"&gt;(defun stream-remove-if-not (test stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(loop for s = stream then (stream-cdr s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;when (stream-null s) do (return s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;when (funcall test (stream-car s)) do&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;       &lt;/span&gt;(return&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;(cons-stream (stream-car s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                      &lt;/span&gt;(stream-remove-if-not test (stream-cdr s))))))&lt;/p&gt;  &lt;p class="Code"&gt;Чтобы опробовать новые возможности в деле, понадобятся еще итератор и функция вывода:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun stream-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;iter&lt;/span&gt; (fun stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(loop for s = stream then (stream-cdr s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;       &lt;/span&gt;while (not (stream-null s))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;       &lt;/span&gt;do (funcall fun (stream-car s))))&lt;/p&gt;  &lt;p class="Code"&gt;(defun print-stream (stream)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(stream-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;iter&lt;/span&gt; #'(lambda (x) (format t "~%~a" x)) stream))&lt;/p&gt;  &lt;p class="Code"&gt;Теперь, можно немного поиграться, чтобы убедиться в работоспособности базовых конструкций:&lt;/p&gt;  &lt;p class="Standard"&gt;CL-USER&gt; (print-stream (cons-stream 1 (cons-stream 2 (cons-stream 3 nil))))&lt;br /&gt;&lt;br /&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;NIL&lt;/p&gt;  &lt;p class="Code"&gt;Подходим к главному препятствию. В схеме мы могли легко определить поток рекурсивно. Например, поток единиц задавался бы так:&lt;/p&gt;  &lt;p class="Standard"&gt;(define ones (cons-stream 1 ones))&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;;; &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Scheme!&lt;/span&gt;&lt;/p&gt;  &lt;p class="Standard"&gt;Несмотря на кажущуюся простоту выражения, компилятор выполняет много работы. Придется это повторить. Я лишь покажу конечный результат. Аналогом на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CL &lt;/span&gt;будет следующее определение:&lt;/p&gt;  &lt;p class="Standard"&gt;(defparameter *ones*&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let ((ones (cons-stream 1 ones)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;ones))&lt;/p&gt;  &lt;p class="Code"&gt;Здесь &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;RECURRENT-LET &lt;/span&gt;похож на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET &lt;/span&gt;с одной переменной. Отличие заключается в том, что к самой переменной можно обращаться внутри определения. То есть, определение может быть рекурсивным.&lt;/p&gt;  &lt;p class="Standard"&gt;Сам макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;RECURRENT-LET &lt;/span&gt;достаточно прост:&lt;/p&gt;  &lt;p class="Standard"&gt;(defmacro recurrent-let (((name value)) &amp;amp;body body)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(let ((x (gensym)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;`(let ((,x (cons nil nil)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;       &lt;/span&gt;(symbol-macrolet ((,name (force (car ,x))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;(setf (car ,x) (delay ,value))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;,@body))))&lt;/p&gt;  &lt;p class="Code"&gt;Вот, во что будет раскрыта внутренняя часть определения параметра *&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ONES*:&lt;/span&gt;&lt;/p&gt;  &lt;p class="Standard"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;(LET ((#:G764 (CONS NIL NIL)))&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(SYMBOL-MACROLET ((ONES (FORCE (CAR #:G764))))&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(SETF (CAR #:G764) (DELAY (CONS-STREAM 1 ONES)))&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;ONES))&lt;/span&gt;&lt;/p&gt;  &lt;p class="Code"&gt;Мы создаем некий объект с одним полем. Это поле содержит ленивое значение переменной. Любое (рекурсивное) обращение к переменной мы трактуем как попытку немедленно вычислить ее значение, если оно еще не вычислено. Трюк работает, потому что мы успеваем присвоить полю объекта ленивое значение прежде первого обращения к этой переменной. Это гарантирует использованный макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;DELAY.&lt;/span&gt;&lt;/p&gt;  &lt;p class="Standard"&gt;Стоит заметить, что данное представление бесконечного потока единиц на самом деле занимает очень мало памяти, поскольку поток ссылается на самого себя.&lt;/p&gt;  &lt;p class="Standard"&gt;Если присмотреться к определению макроса, то можно увидеть, что внутри определения рекурсивной переменной мы можем использовать еще один &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;RECURRENT-LET, &lt;/span&gt;а также любую другую конструкцию, включая &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET, FLET &lt;/span&gt;и&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt; LABELS. &lt;/span&gt;Это открывает путь к вложенным и более сложным взаимно-рекурсивным определениям, что и будет продемонстрировано далее.&lt;/p&gt;  &lt;p class="Standard"&gt;В соответствии с &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;SICP &lt;/span&gt;определим вспомогательные функции:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun add-streams (stream1 stream2)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(stream-map2 #'+ stream1 stream2))&lt;/p&gt;  &lt;p class="Code"&gt;(defun scale-stream (stream factor)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(stream-map #'(lambda (x) (* x factor)) stream))&lt;/p&gt;  &lt;p class="Code"&gt;Далее определим поток целых чисел:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun integers-starting-from (n)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(cons-stream n (integers-starting-from (+ n 1))))&lt;/p&gt;  &lt;p class="Code"&gt;(defparameter *integers-alpha*&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(integers-starting-from 1))&lt;/p&gt;  &lt;p class="Code"&gt;Этот же самый поток мы можем определить иначе:&lt;/p&gt;  &lt;p class="Standard"&gt;(defparameter *integers*&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;((integers (cons-stream 1 (add-streams *ones* integers))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;integers))&lt;/p&gt;  &lt;p class="Code"&gt;Естественно, без определения потока чисел Фибоначчи мое сообщение можно было бы считать неполным:&lt;/p&gt;  &lt;p class="Standard"&gt;(defparameter *fibs*&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;((fibs (cons-stream 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                          &lt;/span&gt;(cons-stream 1&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                                       &lt;/span&gt;(add-streams (stream-cdr fibs)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                                                    &lt;/span&gt;fibs)))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;fibs))&lt;/p&gt;  &lt;p class="Code"&gt;Теперь можем узнать, каким будет &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;1001-ое&lt;/span&gt; число Фибоначчи:&lt;/p&gt;  &lt;p class="Standard"&gt;CL-USER&gt; (stream-ref *fibs* 1001)&lt;br /&gt;70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501&lt;/p&gt;  &lt;p class="Code"&gt;Как и следовало ожидать, ответ был немедленным.&lt;/p&gt;  &lt;p class="Standard"&gt;Следуя &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;SICP, &lt;/span&gt;далее приведу пример определения потока простых чисел. Но прежде мне понадобятся две простые утилиты&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;.&lt;/span&gt;&lt;/p&gt;  &lt;p class="Standard"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;(defun square (n) (* n n))&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;(defun divisible-p (x y) (= (mod x y) 0))&lt;/span&gt;&lt;/p&gt;  &lt;p class="Code"&gt;Сам поток определен ниже. Это также пример взаимно-рекурсивного определения.&lt;/p&gt;  &lt;p class="Standard"&gt;(defparameter *primes*&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;((primes&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(flet ((prime-p (n)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                 &lt;/span&gt;(labels ((iter (ps)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                            &lt;/span&gt;(cond ((&gt; (square (stream-car ps)) n) t)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                                  &lt;/span&gt;((divisible-p n (stream-car ps)) nil)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                                  &lt;/span&gt;(t (iter (stream-cdr ps))))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                   &lt;/span&gt;(iter primes))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;          &lt;/span&gt;(cons-stream&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;           &lt;/span&gt;2&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;           &lt;/span&gt;(stream-remove-if-not #'prime-p (integers-starting-from 3))))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;primes))&lt;/p&gt;  &lt;p class="Code"&gt;Еще один пример — решатель дифференциальных уравнений. Сначала определяем интеграл, где интегрируемая функция передается лениво:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun integral (delayed-integrand initial-value dt)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;((int (cons-stream initial-value&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                         &lt;/span&gt;(let ((integrand (force delayed-integrand)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                           &lt;/span&gt;(add-streams (scale-stream integrand dt)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                                        &lt;/span&gt;int)))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;int))&lt;/p&gt;  &lt;p class="Code"&gt;Теперь сам решатель:&lt;/p&gt;  &lt;p class="Standard"&gt;(defun solve (f y0 dt)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;((y (recurrent-let&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;              &lt;/span&gt;((dy (stream-map f y)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;(integral (delay dy) y0 dt))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;y))&lt;/p&gt;  &lt;p class="Code"&gt;Можем проверить как и в &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;SICP:&lt;/span&gt;&lt;/p&gt;  &lt;p class="Standard"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CL-USER&gt; (stream-ref (solve #'(lambda (y) y) 1 0.001) 1000)&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;2.7169204&lt;/span&gt;&lt;/p&gt;  &lt;p class="Code"&gt;Как видим, примеры достаточно легко ложатся на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CL, &lt;/span&gt;хотя и выглядят несколько иначе. При этом они вполне соответствуют духу ФП. Разве что, использование &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LOOP &lt;/span&gt;несколько необычно для этой области, но это прекрасный пример сочетания разных подходов к программированию. Основа остается несомненно функциональной. Неужели после этого может кто-то по-прежнему утверждать, что &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CL &lt;/span&gt;не является языком функционального программирования?&lt;/p&gt;  &lt;dt&gt;&lt;/dl&gt; &lt;dt&gt;&lt;/dl&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-4283401943692482548?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/4283401943692482548/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/02/streams.html#comment-form' title='Комментарии: 7'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4283401943692482548'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/4283401943692482548'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/02/streams.html' title='Потоки (Streams)'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-7344722027330417760</id><published>2010-02-06T14:18:00.000+03:00</published><updated>2010-02-06T14:34:08.922+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Монада моделирования. Код</title><content type='html'>Открыл новый проект &lt;a href="http://sourceforge.net/projects/aivika/"&gt;Aivika&lt;/a&gt; на SourceForge. Там лежат исходники моделирующей библиотеки для F#. Пожалуй, функцию &lt;b&gt;memo&lt;/b&gt; нужно будет оптимизировать. Вероятно, из-за нее тормозит. Но есть задача важнее. Теперь буду пытаться продвигать идею на инглише.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-7344722027330417760?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/7344722027330417760/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/02/blog-post_06.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7344722027330417760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/7344722027330417760'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/02/blog-post_06.html' title='Монада моделирования. Код'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-3903317525407904555</id><published>2010-02-05T09:33:00.000+03:00</published><updated>2010-02-05T09:40:50.916+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='лисп'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Монада моделирования. Часть вторая. Первые шаги в Common Lisp.</title><content type='html'>&lt;p class="MsoNormal"&gt;Это продолжение &lt;a href="http://dsorokin.blogspot.com/2010/02/blog-post.html"&gt;первой части&lt;/a&gt;, в которой подробно описывалась на языке &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;F&lt;/span&gt;# монада, которую я назвал &lt;i style="mso-bidi-font-style:normal"&gt;монадой моделирования&lt;/i&gt;. С помощью нее легко определять динамические системы на основе дифференциальных уравнений, а затем моделировать такие системы. Причем системы могут быть стохастическими, т.е. могут быть использованы случайные функции при построении дифференциальных уравнений. Более того, с помощью этой монады мы можем строить гибридные системы, где некоторые части модели могут быть описаны явно с помощью итерационных процессов и конечных автоматов. На этом моменте далее я остановлюсь подробнее.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;В общем, это все уже работает на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#, хотя и медленно. Рабочее название соответствующей библиотеки для &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;# – &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Aivika&lt;/span&gt;&lt;/i&gt;. Теперь я перенес базовую часть на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;, назвав новую библиотеку &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Salika&lt;/span&gt;&lt;/i&gt;. Соответствующий монадический макрос назвал как &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;DYNAMICS&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;. Этот макрос полностью соответствует интерфейсу монадических макросов из пакета &lt;a href="http://common-lisp.net/project/cl-monad-macros/"&gt;cl-monad-macros&lt;/a&gt;, о котором я писал в своем &lt;a href="http://dsorokin.blogspot.com/2010/01/common-lisp.html"&gt;блоге&lt;/a&gt; ранее. Вкратце, такие макросы добавляют удобный синтаксический сахар по типу нотации &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;do&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;из хаскеля.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Возьмем простую динамическую систему, описанную на языке &lt;a href="http://sourceforge.net/projects/mapsim/"&gt;MapSim&lt;/a&gt;:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;A = integ (-F, 100);&lt;br /&gt;B = integ (F - G, 0);&lt;br /&gt;C = integ (G, 0);&lt;br /&gt;&lt;br /&gt;F = ka * A;&lt;br /&gt;G = kb * B;&lt;br /&gt;&lt;br /&gt;ka = 1;&lt;br /&gt;kb = 1;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Функция &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;задает интеграл. В первом параметре функции определяется производная по времени. Во втором – начальное значение интеграла. Интегрирование происходит на промежутке &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;starttime&lt;/span&gt; &lt;= &lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt; &lt;=&lt;/i&gt; &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;stoptime&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;с шагом &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dt&lt;/span&gt;&lt;/i&gt;. Здесь &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;A&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt;, &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;B&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt; и &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;C&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt; – интегралы. Их еще иногда называют в системной динамике &lt;i style="mso-bidi-font-style:normal"&gt;резервуарами&lt;/i&gt;. Функции &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt; и &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;G&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt; называют &lt;i style="mso-bidi-font-style: normal"&gt;дополнительными&lt;/i&gt;. В системе также заданы константы &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ka&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;kb&lt;/span&gt;&lt;/i&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;В первой ссылке есть пример того, как эту систему можно задать на языке &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#. Там получается достаточно близко к математической нотации. Во многом благодаря тому, что для типа монады моделирования легко определить арифметические операции и стандартные функции типа синуса и косинуса. Такой подход можно встретить, например, в книге “&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;The&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Haskell&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;School&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;of&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Expression&lt;/span&gt;” автора &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Paul&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Hudak&lt;/span&gt;. В случае же &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;я предпочитаю использовать монадические макросы напрямую, поскольку это порождает в целом более эффективный код. Думаю, что наглядность при этом страдает несильно.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, такую систему мы можем описать на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;следующим образом.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;(defun create-process ()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(let* ((ka 1d0)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;(kb 1d0))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(let* ((integ-a (make-instance 'integ-stock :initial-value 100d0))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;           &lt;/span&gt;(integ-b (make-instance 'integ-stock :initial-value 0d0))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;           &lt;/span&gt;(integ-c (make-instance 'integ-stock :initial-value 0d0)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;(let* ((f (with-dynamics-monad&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                  &lt;/span&gt;(let! ((a (current-value integ-a)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;(unit (* ka a)))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;             &lt;/span&gt;(g (with-dynamics-monad&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                  &lt;/span&gt;(let! ((b (current-value integ-b)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;              &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;(unit (* kb b))))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(setf (outflow integ-a) f)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(setf (inflow integ-b) f)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(setf (outflow integ-b) g)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(setf (inflow integ-c) g)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;(current-value integ-a)))))&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Единственный момент – в примере возвращается лишь интеграл &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;A&lt;/span&gt;&lt;/i&gt;, но как часть всей системы. Здесь монаду можно увидеть в том, как задаются динамические процессы для переменных &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;G&lt;/span&gt;&lt;/i&gt;. Внутри макроса &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;DYNAMICS&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;макро&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;c&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;! эквивалентен стрелке из нотации &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;do&lt;/span&gt;&lt;/i&gt;, а макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;UNIT&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;– функции &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;return&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;для монады. Получается, что &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;G&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;возвращают монады. Более того, выражение &lt;i style="mso-bidi-font-style:normal"&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;current&lt;/span&gt;-&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;value&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integ&lt;/span&gt;-&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;a&lt;/span&gt;)&lt;/i&gt; – тоже монада. Это представления динамических процессов.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Такую систему достаточно просто промоделировать. Нижеследующая функция запускает соответствующую имитацию и возвращает значения интеграла &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;A&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;в основных узлах интегрирования, как это принято в методах Эйлера и Рунге-Кутта.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;(defun run-process ()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(with-dynamics-monad&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(run! (create-process) (make-specs :start-time 0.0 :stop-time 10.0 :method 'runge-kutta-4 :dt 0.1)))))&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Среда лиспа проинтегрирует модель методом Рунге-Кутта четвертого порядка, начиная от точки 0 и заканчивая точкой 10 с основным шагом интегрирования 0,1.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;SALIKA&gt; (run-process)&lt;br /&gt;(100.0d0 90.48374986516933d0 81.8730898966253d0 74.08184186894766d0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;67.03202849220888d0 60.65309299043932d0 54.88119294695767d0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;49.658561349146126d0 44.932928437803035d0 40.65699857475723d0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;36.787976893068794d0 33.28714099238066d0 30.119453392811955d0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;27.253210868708226d0 24.65972715266909d0 22.313045834254343d0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;...)&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Такие дифференциальные уравнения задаются достаточно декларативно, т.е. мы пишем, &lt;i style="mso-bidi-font-style:normal"&gt;что&lt;/i&gt; мы хотим вычислить, при этом почти ничего не говорим о том, &lt;i style="mso-bidi-font-style: normal"&gt;как&lt;/i&gt; вычислить. Разве что упоминаем о порядке зависимости переменных и интегралов друг от друга, но это цена языка программирования с энергичной моделью вычислений. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Тем не менее, во время интегрирования таких уравнений внутри используется сугубо императивная функция мемоизации &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MEMO&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;PROCESS&lt;/span&gt;. Она аналогична функции &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;memo&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;из &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#-вской библиотеки. Обе эти функции принимают динамический процесс, т.е. некоторое значение в монаде моделирования, и возвращают процесс-двойник, который кеширует все значения первого процесса в узлах интегрирования, причем вычисления происходят строго последовательно по узлам. Это открывает путь к построению гибридных моделей, поскольку мы здесь знаем, что первый, т.е. кешируемый процесс будет вычисляться в строго определенном порядке (если нет других ссылок на него). Такой процесс мы можем реализовать как &lt;i style="mso-bidi-font-style: normal"&gt;итерационный&lt;/i&gt;. Это еще одна вещь наряду с недетерминизмом, которую будет трудно или почти невозможно воспроизвести в хаскеле.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Кроме всего прочего, с помощью этой монады достаточно просто моделировать разностные уравнения, а также&lt;i style="mso-bidi-font-style:normal"&gt; конвейеры&lt;/i&gt;. Я думаю, что для имитации последних следует использовать такую иммутабельную структуру данных как &lt;i style="mso-bidi-font-style:normal"&gt;хип&lt;/i&gt;. Там нужно будет хранить полную историю конвейера, и нет ничего лучше структуры, которая бы использовала разделяемые данные, общие сразу для нескольких узлов моделирования. Хип позволит относительно быстро получить следующее значение конвейера, именно полное значение, а не измененное состояние, что вполне функционально в обоих смыслах.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Это все прекрасно, но у моего метода есть большой недостаток. Монада моделирования работает медленно, очень медленно. Например, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MapSim&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;компилирует симуляцию в эффективный байт-код, который выполняется гораздо быстрее. Или, быть может, просто &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MapSim&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;быстр? Но монада моделирования позволяет достаточно просто и легко строить очень сложные гибридные модели. И пока я не определился в своем отношении к изобретению. Просто игрушка для ума или серьезная и полезная вещь?&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Версия для &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;# достаточно зрелая и многое умеет, включая стохастику. Версия для &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;реализует пока лишь интегралы и мемоизацию, причем нет оптимизации по типам. Может быть, потом выложу результаты в свободный доступ. Хотя мне кажется, что описанное в обеих частях достаточно легко воспроизвести, поскольку первая часть содержит полное описание монады моделирования на языке &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;F&lt;/span&gt;#.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-3903317525407904555?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/3903317525407904555/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/02/common-lisp.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/3903317525407904555'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/3903317525407904555'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/02/common-lisp.html' title='Монада моделирования. Часть вторая. Первые шаги в Common Lisp.'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-1159864450732353296</id><published>2010-02-03T10:27:00.000+03:00</published><updated>2010-02-03T10:41:53.965+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='f#'/><category scheme='http://www.blogger.com/atom/ns#' term='моделирование'/><category scheme='http://www.blogger.com/atom/ns#' term='modeling'/><title type='text'>Монада моделирования</title><content type='html'>&lt;p class="MsoNormal"&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;  &lt;p class="MsoNormal"&gt;После начала изучения хаскеля прошедшей осенью придумал простую монаду, которая позволяет моделировать сложные динамические системы, заданные с помощью дифференциальных уравнений, где производная берется по времени. Сначала идея была реализована на хаскеле, и мне особенно нравилось, что благодаря ленивости языка можно было записывать дифференциальные уравнения в произвольном порядке так, как это принято в математике и в специальных моделирующих программах типа &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Vensim&lt;/span&gt; и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ithink&lt;/span&gt;, а также написанных мною &lt;a href="http://www.simtegra.com"&gt;Simtegra MapSys&lt;/a&gt; и &lt;a href="http://sourceforge.net/projects/mapsim/"&gt;MapSim&lt;/a&gt;. Последний является самодостаточным движком предыдущего.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Но с этой реализацией идеи возникла проблема, которую я пока не знаю как красиво разрешить в хаскеле. Моделируемая динамическая система на самом деле может быть стохастической, т.е. недетерминированной. Каждый новый прогон модели может порождать новые результаты. То есть, запускающая имитацию функция не является чистой. Тут на помощь пришел язык &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#, который я начал изучать чуть позже. В нем уже достаточно легко сочетать функциональный и императивный подходы. В итоге это привело к любопытному результату, оформленному в виде библиотеки на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;# с рабочим названием &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Aivika&lt;/span&gt;&lt;/i&gt;, которую я здесь и попытаюсь описать.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Сначала приведу пример простой динамической системы, записанной на языке &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MapSim&lt;/span&gt;&lt;/i&gt;:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;A = integ (-F, 100);&lt;br /&gt;B = integ (F - G, 0);&lt;br /&gt;C = integ (G, 0);&lt;br /&gt;&lt;br /&gt;F = ka * A;&lt;br /&gt;G = kb * B;&lt;br /&gt;&lt;br /&gt;ka = 1;&lt;br /&gt;kb = 1;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь функция &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;задает интеграл. Ее первым параметром является производная по времени, а вторым – начальное значение. То есть, &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;A&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt; является интегралом (еще говорят &lt;i style="mso-bidi-font-style:normal"&gt;резервуаром&lt;/i&gt;), производная которого равна &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dA&lt;/span&gt;/&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dt&lt;/span&gt; = –&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ka&lt;/span&gt;*&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;A&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;)&lt;/i&gt;. В начальный момент времени значение &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;A&lt;/span&gt;(&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;t&lt;/span&gt;&lt;sub&gt;0&lt;/sub&gt;)&lt;/i&gt; равно 100. Это задача Коши.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Такая система может быть интегрирована методами Эйлера и Рунге-Кутта второго и четвертого порядков. Задается начальное время &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;starttime&lt;/span&gt;&lt;/i&gt;, конечное &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;stoptime&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и основной шаг интегрирования &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dt&lt;/span&gt;&lt;/i&gt;. Затем временная шкала разбивается на узлы: &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;starttime&lt;/span&gt;, &lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;starttime&lt;/span&gt; + &lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;dt&lt;/span&gt;, &lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;starttime&lt;/span&gt; + 2*&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dt&lt;/span&gt;, …, &lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;stoptime&lt;/span&gt;&lt;/i&gt;. В случае методов Рунге-Кутта создаются еще дополнительные внутренние под-узлы. В итоге каждый узел сетки интегрирования однозначно задается парой целых чисел: номером итерации и номером фазы. Метод Эйлера имеет всего одну фазу. Метод Рунге-Кутта второго порядка – две фазы. Метод четвертого порядка – четыре фазы.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type Iteration = int&lt;br /&gt;type Phase = int&lt;br /&gt;type Method = Euler | RungeKutta2 | RungeKutta4&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь и далее я буду использовать &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;# для записи кода.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Система может быть стохастической. Поэтому используется генератор случайных чисел, которым можно управлять, задавая параметр следующего типа&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;Randomness&lt;/span&gt;&lt;span style="mso-ansi-language: RU"&gt; = &lt;/span&gt;&lt;span lang="EN-US"&gt;SimpleRnd&lt;/span&gt;&lt;span style="mso-ansi-language: RU"&gt; | &lt;/span&gt;&lt;span lang="EN-US"&gt;StrongRnd&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Тогда параметры моделирования, их еще называют спеками моделирования, могут быть описаны значением типа&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type Specs = {&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;spcStartTime: float; spcStopTime: float; spcDT: float;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;spcMethod: Method; spcRandomness: Randomness&lt;br /&gt;}&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Для нашего примера мы могли бы взять следующие параметры: начальное время – 0, конечное время – 10, основной шаг интегрирования – 0,1, метод – Рунге-Кутта четвертого порядка, а режим генератора – «сильная» псевдо-случайность, хотя последний никак не используется в этой модели.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let specs = {&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;spcStartTime=0.0; spcStopTime=10.0; spcDT=0.1;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;spcMethod=RungeKutta4; spcRandomness=StrongRnd&lt;br /&gt;}&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Саму систему мы можем начать описывать с констант &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ka&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;kb&lt;/span&gt;&lt;/i&gt;.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;ka&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt; = 1.0&lt;br /&gt;&lt;/span&gt;&lt;span lang="EN-US"&gt;let&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;kb&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt; = 1.0&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Затем объявляем интегралы и задаем для них начальные значения.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let a = Integ 100.0&lt;br /&gt;let b = Integ 0.0&lt;br /&gt;let c = Integ 0.0&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Каждый из трех интегралов имеет свойства &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Value&lt;/span&gt;&lt;/b&gt;, &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Inflow&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Outflow&lt;/span&gt;&lt;/b&gt;. Эти свойства связаны дифференциальными уравнениями, одно из которых я покажу на примере интеграла &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;a&lt;/span&gt;&lt;/i&gt;:&lt;/p&gt;  &lt;p class="MsoNormal" align="center" style="text-align:center"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;d/dt (a.Value) = (a.Inflow – a.Outflow)&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Такие же уравнения имеют место для других интегралов. Свойство &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Value&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;возвращает текущее значение интеграла, и оно доступно только по чтению. Свойства &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Inflow&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Outflow&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;можно менять. Они оба вместе задают производную. По-умолчанию, они представляют нулевые значения. То есть, если мы не зададим ни одно из свойств &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Inflow&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Outflow&lt;/span&gt;&lt;/b&gt;, то производная будет равна нулю, а интеграл окажется константой. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Продолжаем описание задачи с определения дополнительных функций, которые зависят уже от текущих значений интегралов.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let f = ka * a.Value&lt;br /&gt;let g = kb * b.Value&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;И завершаем описание задачи собственно самими дифференциальными уравнениями:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;a.Inflow &lt;- -f&lt;br /&gt;b.Inflow &lt;- f – g&lt;br /&gt;c.Inflow &lt;- g&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Удивительно, но это все – что нам нужно, чтобы немедленно запросить библиотеку промоделировать, скажем, переменную &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;a&lt;/span&gt;&lt;/i&gt; и вернуть результат:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;&gt; runDynamics a.Value specs;;&lt;br /&gt;val it : float [] =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|100.0; 90.48375; 81.87309014; 74.0818422; 67.03202889; 60.65309344;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;54.88119344; 49.65856187; 44.93292897; 40.65699912; 36.78797744;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;33.28714154; 30.11945393; 27.2532114; 24.65972767; 22.31304633;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;20.18968106; 18.26838054; 16.52991577; 14.95688766; 13.53355284;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;12.24566612; 11.08033792; 10.02590526; 9.071815051; 8.208518451;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;7.427375314; 6.720567711; 6.081021686; 5.50233646; 4.978720367;...|]&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Более того, мы можем запросить результаты не только для одной переменной, но и для целой группы, попросив включить в список и модельное время:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span style="mso-ansi-language:RU"&gt;&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;runDynamics&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;Dynamics&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;.&lt;/span&gt;&lt;span lang="EN-US"&gt;ofArray&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt; [| &lt;/span&gt;&lt;span lang="EN-US"&gt;time&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;; &lt;/span&gt;&lt;span lang="EN-US"&gt;a&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;.&lt;/span&gt;&lt;span lang="EN-US"&gt;Value&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;; &lt;/span&gt;&lt;span lang="EN-US"&gt;b&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;.&lt;/span&gt;&lt;span lang="EN-US"&gt;Value&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;; &lt;/span&gt;&lt;span lang="EN-US"&gt;c&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;.&lt;/span&gt;&lt;span lang="EN-US"&gt;Value&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt; |] &lt;/span&gt;&lt;span lang="EN-US"&gt;specs&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;;;&lt;br /&gt;[|[|0.0; 100.0; 0.0; 0.0|];&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|0.1; 90.48375; 9.048333333; 0.4679166667|];&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|0.2; 81.87309014; 16.37454263; 1.752367234|];&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|0.3; 74.0818422; 22.22445032; 3.693707481|];&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|0.4; 67.03202889; 26.81268809; 6.155283021|];&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;[|0.5; 60.65309344; 30.32640707; 9.020499487|]; ...|]&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Поразительная краткость, не правда ли?&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Все это стало возможным благодаря использованию монады моделирования, которая является вариацией стандартной монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;. Еще меня натолкнула на ее написание замечательная книга «&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;The&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Haskell&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;School&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;of&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Expression&lt;/span&gt;» автора &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Paul&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Hudak&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, мы моделируем некоторый динамический процесс, который в каждый момент времени возвращает некоторое значение типа &lt;i style="mso-bidi-font-style: normal"&gt;'&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;a&lt;/span&gt;&lt;/i&gt;. Обозначим этот полиморфный тип как &lt;b style="mso-bidi-font-weight:normal"&gt;Dynamics&lt;'a&gt;&lt;/b&gt;.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;[&lt;sealed&gt;]&lt;br /&gt;type Dynamics&lt;'a&gt; (iter: Iterator&lt;'a&gt;) =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;member d.Iterator = iter&lt;br /&gt;&lt;br /&gt;let iterator (p: Dynamics&lt;'a&gt;) = p.Iterator&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Внутри этого типа сидит итератор, который по заданным спекам моделирования возвращает в каждом узле интегрирования некоторое значение. Если вы помните, такие узлы однозначно определялись парой целых чисел: номером итерации и фазой.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type Iterator&lt;'a&gt; = Run -&gt; Specs -&gt; Iteration -&gt; Phase -&gt; 'a&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;То есть, итератор – это просто функция. Здесь появился новый тип &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Run&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;c&lt;/span&gt; которым связан недетерминизм и не только. В упрощенном виде (после удаления некоторых оптимизаций) этот тип выглядит так.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type Run () =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let mutable disposed = false&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let disposedEvent = new Event&lt;_&gt;()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;member x.Disposed = disposedEvent.Publish&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;member x.IsDisposed = disposed&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;interface IDisposable with&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;member x.Dispose() =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;if not disposed then&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;          &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;disposedEvent.Trigger()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;disposed &lt;- true&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Когда мы запускаем имитацию, мы создаем уникальный экземпляр типа &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Run&lt;/span&gt;&lt;/b&gt;. Затем во время интегрирования мы передаем это значение в итератор динамического процесса. Ориентируясь на такое значение, любая функция может создать некоторые внутренние данные, которые будут актуальны, пока существует значение типа &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Run&lt;/span&gt;&lt;/b&gt;. Например, это может быть таблица кеширования предыдущих значений для интеграла внутри объекта типа &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Integ&lt;/span&gt;&lt;/b&gt;. Когда имитация заканчивается, значение типа &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Run&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;разрушается, но предварительно вызывается метод &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Dispose&lt;/span&gt;&lt;/b&gt;, который посылает сигнал &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Disposed&lt;/span&gt;&lt;/b&gt;. Одна из внутренних функций интеграла или уровнем ниже ловит этот сигнал, и разрушает таблицу кеширования. Так достигается следующее. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Запуски динамического процесса могут давать разные результаты. Но внутри одного запуска мы можем запоминать и использовать предыдущие вычисления по шкале времени, актуальные для этого запуска. После окончания мы можем удалить все использованные внутренние структуры. Это открывает путь к кешированию и тому подобным методикам.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Сам запуск динамического процесса очень прост. Его вызов уже использовался в примерах выше.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let runDynamics d specs = [|&lt;br /&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;use r = new Run ()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let f1 = iterator d r specs&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let f2 = fun n -&gt; f1 n 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;for i = 0 to (Specs.iterations specs - 1) do yield f2 i&lt;br /&gt;|]&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь функция &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;iterations&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;возвращает общее число итераций по заданным спекам. &lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;На выходе нас интересуют значения динамического процесса в основных узлах сетки интегрирования – поэтому запрашивается нулевая фаза. Ниже будет приведено определение функции &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;, на примере которого можно понять то, как работают фазы.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Собственно, все. Дальше монада моделирования определяется просто. Ее функции &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;return&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;bind&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;почти такие же, как и в случае монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;[&lt;sealed&gt;]&lt;br /&gt;type DynamicsBuilder() =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;member d.Return (a) =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;new Dynamics&lt;'a&gt; (fun r s n ph -&gt; a)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;member d.Bind (m, k) =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;new Dynamics&lt;'b&gt; (fun r s n ph -&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let a = iterator m r s n ph&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let m' = k a&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;iterator m' r s n ph)&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Тогда мы можем определить свой &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;workflow&lt;/span&gt;&lt;/i&gt;. Назовем его &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;dynamics&lt;/span&gt;&lt;/b&gt;.&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;Он позволяет использовать синтаксический сахар в &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#, похожий на нотацию &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;do&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;из хаскеля, где &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;let&lt;/span&gt;!&lt;/b&gt; будет эквивалентен стрелке.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;let&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;dynamics&lt;/span&gt;&lt;span style="mso-ansi-language: RU"&gt; = &lt;/span&gt;&lt;span lang="EN-US"&gt;new&lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: RU"&gt; &lt;/span&gt;&lt;span lang="EN-US"&gt;DynamicsBuilder&lt;/span&gt;&lt;span style="mso-ansi-language: RU"&gt;()&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Более того, мы можем определить арифметические операции и основные функции типа синуса в соответствии с образцом:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;type Dynamics&lt;'a&gt; with&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;static member (+) (a: Dynamics&lt;float&gt;, b: Dynamics&lt;float&gt;) =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;Dynamics.lift2 (+) a b&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь функция &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;lift&lt;/span&gt;2&lt;/b&gt; достаточно идиоматична. Она берет некоторую функцию и два значения, обернутые в монаду. Значения извлекаются, к ним применяется функция, и результат снова заворачивается в монаду.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;module Dynamics =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let lift2 f m1 m2 = dynamics {&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let! a1 = m1&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let! a2 = m2&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;return f a1 a2}&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Но я использовал версию оптимальнее, которая уже учитывает внутреннее устройство монады:&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;module Dynamics =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;let lift2 f m1 m2 = Dynamics&lt;_&gt; (fun r s n ph -&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let a1 = iterator m1 r s n ph&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let a2 = iterator m2 r s n ph&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;in f a1 a2)&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Этих сведений о монаде достаточно, чтобы определить интегралы и стохастические функции, а также утилиту мемоизации &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;memo&lt;/span&gt;&lt;/b&gt;, которая кеширует предыдущие значения заданного динамического процесса внутри одного запуска, вычисляя их строго последовательно по узлам интегрирования.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;module Dynamics =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;val memo: Dynamics&lt;'a&gt; -&gt; Dynamics&lt;'a&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь для краткости приведу лишь функцию &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;– аналог одноименной функции &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;MapSim&lt;/span&gt;&lt;/i&gt;, причем только для интегрирования по методу Рунге-Кутта второго порядка. Для других методов определены аналогичные внутренние функции &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;integEuler&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integRK&lt;/span&gt;4&lt;/b&gt;.&lt;/p&gt;  &lt;p class="a"&gt;&lt;span style="mso-ansi-language:RU"&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span lang="EN-US"&gt;let integ (f: Dynamics&lt;float&gt;) (i: Dynamics&lt;float&gt;) =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;// integEuler and integRK4&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let integRK2 (y: Dynamics&lt;_&gt;) (f: Dynamics&lt;_&gt;) (i: Dynamics&lt;_&gt;) r s =&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let vy n = iterator y r s n 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let vi n = iterator i r s n 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let k1 n = iterator f r s n 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;let k2 n = iterator f r s n 1&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;fun n ph -&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;match n, ph with&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;| 0, 0 -&gt; vi 0&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;| n, 0 -&gt; vy (n-1) + s.spcDT/2.0 * (k1 (n-1) + k2 (n-1))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;| n, 1 -&gt; vy n + s.spcDT * k1 n&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;| _ -&gt; failwithf "integRK2: incorrect phase = %i" ph&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;let rec y = Dynamics.memo z&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;and z = Dynamics&lt;_&gt; (fun r s -&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;            &lt;/span&gt;match s.spcMethod with&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;| Euler -&gt; integEuler y f i r s&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;| RungeKutta2 -&gt; integRK2 y f i r s&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;| RungeKutta4 -&gt; integRK4 y f i r s)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;in y&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Но одной функции &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;недостаточно в &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#, потому что нужно разруливать как-то рекурсивные связи. Поэтому придумал трюк с отдельным типом &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Integ&lt;/span&gt;&lt;/b&gt; и тремя его свойствами &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Value&lt;/span&gt;&lt;/b&gt;,&lt;b style="mso-bidi-font-weight:normal"&gt; &lt;/b&gt;&lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Inflow&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;/b&gt;и&lt;b style="mso-bidi-font-weight:normal"&gt; &lt;/b&gt;&lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Outflow&lt;/span&gt;&lt;/b&gt;. Сам тип &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Integ&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;использует внутри функцию &lt;b style="mso-bidi-font-weight:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;integ&lt;/span&gt;&lt;/b&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, монада моделирования позволяет достаточно просто задавать дифференциальные уравнения и интегрировать их. Это очень декларативно. Но я думаю, что реальная сила этого метода будет проявляться при построении гибридных моделей. Например, с помощью функции &lt;b style="mso-bidi-font-weight: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;memo&lt;/span&gt;&lt;/b&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;мы можем определять некоторые динамические процессы через конечный автомат, что уже достаточно императивно.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;В заключение несколько слов о скорости имитации. Она ужасна. Мои тесты показали, что библиотека &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Aivika&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;медленнее &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;MapSim&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;в десятки или сотни раз, хотя и там, и там – .&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;NET&lt;/span&gt;. И автор один и тот же &lt;span lang="EN-US" style="font-family:Wingdings;mso-ascii-font-family: Calibri;mso-ascii-theme-font:minor-latin;mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin;mso-ansi-language:EN-US;mso-char-type:symbol; mso-symbol-font-family:Wingdings"&gt;&lt;span style="mso-char-type:symbol;mso-symbol-font-family: Wingdings"&gt;J&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Теперь вот думаю, а не написать ли мне подобное на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;– там некоторые вещи можно сделать быстрее за счет более умной кодогенерации.&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-1159864450732353296?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/1159864450732353296/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/02/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1159864450732353296'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/1159864450732353296'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/02/blog-post.html' title='Монада моделирования'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-8756700486175997233</id><published>2010-01-28T12:34:00.001+03:00</published><updated>2010-01-28T12:41:22.385+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='лисп'/><title type='text'>Алгебраические типы данных в Common Lisp</title><content type='html'>&lt;p class="MsoNormal"&gt;В продолжение функциональной темы. В ходе &lt;a href="http://www.linux.org.ru/jump-message.jsp?msgid=4485563&amp;amp;cid=4490527"&gt;беседы&lt;/a&gt; у меня возникла идея того, как можно создавать и обрабатывать так называемые &lt;i style="mso-bidi-font-style:normal"&gt;алгебраические типы данных (&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;algebraic&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;/i&gt;&lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;data&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;types&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;- &lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ADT&lt;/span&gt;) &lt;/i&gt;вроде таких&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;data TaggedType a = NoneValue&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                  &lt;/span&gt;| SingleValue a&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                  &lt;/span&gt;| DoubleValue (a, a)&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Здесь &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;NoneValue&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;SingleValue&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;DoubleValue&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;являются также конструкторами данных. Компилятор хаскеля по такому определению автоматически создает одноименные функции. Мы их тоже создадим. Будем делать все через списки. Первым элементом списка будет идти символическое имя конструктора, т.е. тег. Затем в списке будут идти данные. Это позволит нам различать значения.&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(defun none-value ()&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(list 'none-value))&lt;br /&gt;&lt;br /&gt;(defun single-value (a)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(list 'single-value a))&lt;br /&gt;&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;(defun double-value (a1 a2)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(list 'double-value a1 a2))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Как видим, все очень просто. Теперь мы можем вручную устроить &lt;i style="mso-bidi-font-style:normal"&gt;сопоставление с образцом (&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;pattern&lt;/span&gt;-&lt;/i&gt;&lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;matching&lt;/span&gt;)&lt;/i&gt;.&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(defun test-tagged-type (v)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(ecase (car v)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(none-value (format t "NoneValue "))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(single-value (format t "SingleValue ~A" (cadr v)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(double-value (format t "DoubleValue (~A, ~A)" (cadr v) (caddr v)))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Писать каждый раз такой код – дело утомительное и ненужное. Поэтому я придумал вспомогательные утилиты.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;DEFINE&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ADT&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;позволяет автоматически генерировать конструктор данных для &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ADT&lt;/span&gt;. Получается та же самая функция, создающая такой же список.&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(defmacro define-adt (name &amp;amp;rest args)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;`(defun ,name (,@args)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;     &lt;/span&gt;(list ',name ,@args)))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Следующий макрос позволяет более наглядно сопоставлять с образцом. Его имя похоже на имена стандартных макросов &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CASE&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;CCASE&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ECASE&lt;/span&gt;. Приставка &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ADT&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;указывает, что работа идет с алгебраическими типами данных.&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(defmacro adt-case (value &amp;amp;body cs)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(let* ((t-defined nil)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;(ps (loop for c in cs collect&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                  &lt;/span&gt;(cond&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;((eql (car c) 't)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                     &lt;/span&gt;(setf t-defined t)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                     &lt;/span&gt;(append '(t) (cdr c)))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                    &lt;/span&gt;(t&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;               &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;(when t-defined&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                       &lt;/span&gt;(error "The T clause can be only the last in the form."))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                     &lt;/span&gt;(destructuring-bind ((name &amp;amp;rest args) &amp;amp;body body) c&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                       &lt;/span&gt;(adt-pattern value name args body)))))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(if t-defined&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;`(case (car ,value) ,@ps)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;`(ecase (car ,value) ,@ps))))&lt;br /&gt;&lt;br /&gt;(defun adt-pattern (value name args body)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;`(,name&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;,(if (null args)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;`(progn ,@body)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;         &lt;/span&gt;`(destructuring-bind (,@args) (cdr ,value) ,@body))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Тогда наш исходный код превращается в легко-читаемый и более краткий. Более того, мы ничего не должны знать о том, что там где-то внутри используются списки для реализации &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ADT&lt;/span&gt;. Более&lt;span style="mso-ansi-language:EN-US"&gt; &lt;/span&gt;высокий&lt;span style="mso-ansi-language:EN-US"&gt; &lt;/span&gt;уровень&lt;span style="mso-ansi-language: EN-US"&gt; &lt;/span&gt;абстракции!&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(define-adt none-value)&lt;br /&gt;(define-adt single-value a)&lt;br /&gt;(define-adt double-value a1 a2)&lt;br /&gt;&lt;br /&gt;(defun test-tagged-type (v)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(adt-case v&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((none-value) (format t "NoneValue"))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;   &lt;/span&gt;((single-value a) (format t "SingleValue ~a" a))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((double-value a1 a2) (format t "DoubleValue (~a, ~a)" a1 a2))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Сопоставление с образцом было бы неполным, если бы не было замены хаскелевского шаблона “_”. Я выбрал в качестве аналога лисповский терм &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;T&lt;/span&gt;. Соответствующее условие должно быть самым последним в списке, иначе компилятор выдаст ошибку.&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;(defun test-tagged-type-2 (v)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(adt-case v&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((none-value) (format t "NoneValue"))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;(t (format t "Not NoneValue"))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;И, наконец, приведу функцию, которая берет значение типа &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;TaggedType&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и возвращает либо &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;NIL&lt;/span&gt;, либо новую &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;CONS-&lt;/span&gt;пару&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;.&lt;/span&gt;&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;(defun tagged-type-&gt;cons (v)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(adt-case v&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((none-value) nil)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((single-value a) (list a))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;    &lt;/span&gt;((double-value a1 a2) (cons a1 a2))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Пример&lt;span style="mso-ansi-language:EN-US"&gt; &lt;/span&gt;использования&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;:&lt;/span&gt;&lt;/p&gt;  &lt;div style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-themecolor:accent1;mso-background-themetint: 51"&gt;  &lt;p class="a" style="background:#DBE5F1;mso-background-themecolor:accent1; mso-background-themetint:51"&gt;&lt;span lang="EN-US"&gt;CL-USER&gt; (tagged-type-&gt;cons (double-value 1 2))&lt;br /&gt;(1 . 2)&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-8756700486175997233?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/8756700486175997233/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/01/common-lisp_28.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8756700486175997233'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/8756700486175997233'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/01/common-lisp_28.html' title='Алгебраические типы данных в Common Lisp'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-6824143957586224040</id><published>2010-01-22T14:48:00.000+03:00</published><updated>2010-01-22T14:51:24.005+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='лисп'/><title type='text'>Монадические макросы для Common Lisp</title><content type='html'>&lt;p class="MsoNormal"&gt;Изобрел монадические макросы для &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Common&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Lisp&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и оформил их в виде пакета &lt;a href="http://common-lisp.net/project/cl-monad-macros/"&gt;cl-monad-macros&lt;/a&gt;. На мой взгляд, получилась довольно интересная вещь, основным достоинством которой является высокая эффективность генерируемого кода. Здесь было бы интересно посостязаться с хаскелевскими компиляторами. Потом поместил объявление на &lt;a href="http://groups.google.com/group/comp.lang.lisp/browse_thread/thread/b105300c36f1c516%23"&gt;comp.lang.lisp&lt;/a&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Основная идея состоит в том, что мы задаем монадические макросы типа &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;, которые внутри себя через &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MACROLET&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;определяют унифицированные макросы &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;UNIT&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;FUNCALL&lt;/span&gt;!, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;! и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;!. Макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;UNIT&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;– это хасклевская функция &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;return&lt;/span&gt;&lt;/i&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;FUNCALL&lt;/span&gt;! – это &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;bind&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;с обратным порядком параметров (=&lt;&lt;), &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;! – альтернатива стрелке, а &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;! – замена хаскелевского &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;then&lt;/span&gt;&lt;/i&gt; (&gt;&gt;). Причем &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;LET&lt;/span&gt;! по виду похож на стандартный &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;*, а &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;! – на стандартный &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;. Более того, в случае монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Identity&lt;/span&gt; (макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IDENTITY&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;) макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;! совпадает с &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LET&lt;/span&gt;*, &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;PROGN&lt;/span&gt;! – с &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;FUNCALL&lt;/span&gt;! – &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;c&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;FUNCALL&lt;/span&gt;, а &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;UNIT&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;становится эквивалентным обычной функции &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IDENTITY&lt;/span&gt;. Все это позволяет писать код в единой нотации – меняем только монадические макросы типа &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Например, макрос &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LIST&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;задает монаду &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;List&lt;/span&gt;. Следующая функция возвращает все возможные перестановки заданного списка.&lt;/p&gt;  &lt;div  style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-thememso-background-themetint: 51color:accent1;"&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;(defun perms (xs)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(with-list-monad&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;      &lt;/span&gt;(if (null xs)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;(unit nil)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;        &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt;  &lt;/span&gt;(let! ((y xs)&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;&lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;(ys (perms (remove y xs :count 1))))&lt;br /&gt;&lt;span style="mso-spacerun:yes"&gt;                &lt;/span&gt;(unit (cons y ys))))))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;Функцию можно протестировать:&lt;/p&gt;  &lt;div  style="mso-element:para-border-div;border:solid windowtext 1.0pt; mso-border-alt:solid windowtext .5pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; background:#DBE5F1;mso-background-thememso-background-themetint: 51color:accent1;"&gt;  &lt;p class="a"&gt;&lt;span lang="EN-US"&gt;CL&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;-&lt;/span&gt;&lt;span lang="EN-US"&gt;USER&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt;&gt; (&lt;/span&gt;&lt;span lang="EN-US"&gt;perms&lt;/span&gt;&lt;span style="mso-ansi-language:RU"&gt; '(1 2 3))&lt;br /&gt;((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))&lt;/span&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;p class="MsoNormal"&gt;По своей сути &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;LET&lt;/span&gt;! &lt;span style="mso-spacerun:yes"&gt; &lt;/span&gt;– это обобщенный случай &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;List&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Comprehension&lt;/span&gt;. &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;PROGN&lt;/span&gt;! создает последовательность вычислений. Самое интересное, что реализация всех этих макросов в общем случае проста как топор. За это отвечает монадический макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Пакет &lt;strong&gt;cl-monad-macros&lt;/strong&gt; предоставляет также готовые монадические макросы для монад &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;Identity&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;List&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Maybe&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;State&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Writer&lt;/span&gt;. Последняя монада несколько отличается от канона, но суть та же. Такие специализированные макросы порождают намного более эффективный код для своих монад, чем обобщенный макрос &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Были сложности с монадными трансформерами. Для них, кстати, отдельные макросы – необходимость. В пакете есть макросы для трансформеров &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;State&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Writer&lt;/span&gt;. Это параметризуемые монадические макросы, где параметром является другой монадический макрос. Довольно мощная абстракция, которая позволяет комбинировать несколько монад в одной.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Если пытаться использовать монадические макросы для транформеров непосредственно в коде, то легко загнать лисповский компилятор в ступор. Дело в том, что эти макросы при раскрытии создают множество вложенных &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MACROLET&lt;/span&gt;-ов. У компиляторов от этого сносит голову напрочь. Но пока мне такая кодо-генерация представляется единственно возможной для трансформеров, если делать именно через макросы, а не &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;generic&lt;/span&gt;-функции. Но, к счастью, есть простое решение. Назвал его &lt;i style="mso-bidi-font-style:normal"&gt;упрощением (редукцией)&lt;/i&gt; монадических макросов. С обычными же непараметризуемыми макросами типа &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;MONAD&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;WITH&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LIST&lt;/span&gt;-&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;MONAD&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;все нормально.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Теперь вот думаю дополнить монадические макросы вспомогательными макросами. Нужны аналоги хаскелевских полиморфных функций типа &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;fmap&lt;/span&gt;&lt;/i&gt;, &lt;i style="mso-bidi-font-style:normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;sequence&lt;/span&gt;&lt;/i&gt;, &lt;i style="mso-bidi-font-style: normal"&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;join&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и т.д. Еще нужно что-то придумать для циклов. В хаскеле этого уже нет, но зато есть в &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Это все собираюсь использовать для симуляции моделей системной динамики. Обнаружил, что динамический процесс может быть определен как вариация монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;. Что важно, это позволяет задавать задачи моделирования декларативно, т.е. на высоком уровне абстракции. Я уже написал такую библиотеку на &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#, но меня не устраивает ее скорость – слишком много накладных расходов, связанных с монадами. Надеюсь, что лисповская реализация на основе придуманных мною монадических макросов будет быстрее, во многом благодаря особой технике генерации кода для монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Reader&lt;/span&gt;. Там получается, что &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;FUNCALL&lt;/span&gt; и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;LAMBDA&lt;/span&gt; чередуются друг с другом, то есть их можно сократить, но об этом написано уже у меня в документации к пакету &lt;strong&gt;cl-monad-macros&lt;/strong&gt;&lt;a href="http://common-lisp.net/project/cl-monad-macros/"&gt;&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-6824143957586224040?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/6824143957586224040/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2010/01/common-lisp.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/6824143957586224040'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/6824143957586224040'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2010/01/common-lisp.html' title='Монадические макросы для Common Lisp'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8641042312683198534.post-5523379217567170057</id><published>2009-12-02T20:31:00.000+03:00</published><updated>2010-01-19T07:36:53.936+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='хаскель'/><title type='text'>Следствия чистоты языка</title><content type='html'>&lt;p class="MsoNormal"&gt;  &lt;p class="MsoNormal"&gt;Недавно открыл для себя мир функциональных языков. Увлечен. Особенно удивил Хаскель. Последний довольно необычен и не похож на другие языки. Решил для себя привести свои мысли о нем в порядок и записать в виде небольшого изложения. У меня получается, что Хаскель – очень продуманный язык, где разные концепции взаимосвязаны и следуют друг от друга. Отправной точкой служит чистота языка.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Другим мотивом послужило повышение внимания к теме Хаскеля со стороны некоторых программистов [&lt;a href="http://lisp-univ-etc.blogspot.com/2009/11/blog-post.html"&gt;lisp-univ-etc&lt;/a&gt;]. Есть ряд заблуждений и мифов относительно языка. Поэтому хотелось бы некоторые вещи прояснить для самого себя.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, начало следует.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;1. Чистота языка и ленивость.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;i style="mso-bidi-font-style:normal"&gt;Чистота&lt;/i&gt; языка подразумевает, что нет деструктивного присваивания и побочных эффектов. Поэтому нам становится все равно, где, когда и как вычислять значение – ответ будет всегда один и тот же. Тогда мы можем использовать либо энергичную стратегию вычислений как в общепринятых языках, либо ленивую. Причем для ленивости нужна именно чистота. Иначе бы из-за побочных эффектов значения разнились бы и зависели от времени и способа вычисления. Что теряло бы смысл. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, чистый язык может использовать либо энергичную, либо ленивую стратегию вычислений. Ленивость как единственно возможная стратегия вычислений имеет смысл только для чистого языка.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;2. Чистота монад.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Монады строятся на основе чистых функций, и потому они чисты. Несколько особняком стоят монады &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;IO&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;ST&lt;/span&gt;, которые используют внутренности компилятора. Монада &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;ST&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;опирается на квантор всеобщности при своем запуске, который и гарантирует чистоту. Монада же &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;по большому счету должна запускаться всего один раз самой исполняемой средой и на самом верхнем уровне, используя значение &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;main&lt;/span&gt;. Этот запуск осуществляется всего один раз, и именно он привносит побочный эффект. Но запуск происходит как бы уже “за пределами” языка, и все формально остается чистым.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;3. Монады как вычисления.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;i style="mso-bidi-font-style:normal"&gt;Монады&lt;/i&gt; можно воспринимать как &lt;i style="mso-bidi-font-style:normal"&gt;вычисления, &lt;/i&gt;обычно отложенные. Когда нам нужно выразить побочный эффект мы не возвращаем конкретное вычисленное значение &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt;, которое кстати может разниться от запуска к запуску. Вместо этого мы возвращаем само вычисление, которое уже позволяет получить &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; при некоторых начальных условиях. Такое вычисление можно обернуть в монаду &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;m&lt;/span&gt;. Тогда само вычисление обозначим как &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt;.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Для получения результата вычисления существуют многочисленные функции, чьи названия начинаются со слова &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;run&lt;/span&gt;: &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;runST&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;runState&lt;/span&gt;, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;runWriter&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и т.д.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Другой способ запуска вычисления – это передача самого вычисления другому вычислению. Тогда первое вычисление становится частью второго. Здесь используются функции &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;lift&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;liftIO&lt;/span&gt;. Это приводит нас к понятию &lt;i style="mso-bidi-font-style: normal"&gt;монады-трансформера&lt;/i&gt;. Большинство монад, за исключением &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;, имеют свои дубликаты – трансформеры, которые сами являются вычислениями и дополнительно могут быть контейнерами для других вычислений (монад). Монада &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;является крайней, и у нее не может быть трансформера.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Кроме этого, вычисления могут непосредственно содержать вычисленные данные. Например, монады &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;List&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;и &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Identity&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;так и делают. Поэтому дополнительный запуск вычисления им не нужен.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;4. Программа как вычисление.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Программа на Хаскеле заключена в значении &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;main&lt;/span&gt;, которое имеет тип &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt; (). То есть, это - некоторое вычисление в монаде &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;, которое возвращает значение типа (). Причем само значение &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;main&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;является чистым. Для его получения не могут быть использованы никакие побочные эффекты, даже если то вычисление, которое задает &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;main&lt;/span&gt;, их производит. Ведь мы не вычисляем, а возвращаем лишь вычисление!&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Это принципиально отличается от большинства языков. На &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;C&lt;/span&gt;++ функция &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;main&lt;/span&gt;() выполняет последовательно программу. В Хаскеле &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;main&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;возвращает отложенное вычисление, которое еще надо запустить. Такой запуск осуществляется извне самой исполняющей средой Хаскеля. Просто другой взгляд на вещи.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;5. Выражение побочного эффекта через чистые функции.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Мы не можем в чистом языке вызвать функцию, производящую побочный эффект. Но как показывает пример &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;main&lt;/span&gt;, мы можем вернуть отложенное вычисление, которое уже при своем запуске произведет заданный побочный эффект. Это – ключевая идея. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Здесь можно привести аналогию с рядом Фурье из математического и функционального анализа. Заданную сложную математическую функцию можно разложить в ряд примитивных функций. Теперь представим себе, что заданную сложную программу, производящую побочный эффект, мы раскладываем в ряд вычислений, каждое из которых выражается на чистом языке программирования. Красиво, не правда ли?&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Вопрос в том, насколько полным является базис, т.е. в данном случае набор примитивов, возвращающих значения в монаде &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;. Поскольку в Хаскеле есть &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;FFI&lt;/span&gt;, то мы можем считать такой базис достаточно полным.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;6. Почему ленивость?&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Чистый язык не обязан быть ленивым. Он вполне мог бы быть энергичным. Но тут возникает проблема.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;main&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;возвращает отложенное вычисление в монаде &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;IO&lt;/span&gt;. Но будь язык энергичным, мы должны были бы полностью сконструировать в памяти все вычисление. И сделать это сразу после запуска до начала реального выполнения программы. Это было бы неэффективно как с точки зрения скорости исполнения, так и с позиции потребления памяти.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Поэтому эффективным решением является ленивость. Мы тут же возвращаем еще нераскрытое до конца значение &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;main&lt;/span&gt;, которое и раскручивается по мере необходимости. Это позволяет сразу приступить к исполнению программы, при этом благодаря &lt;i style="mso-bidi-font-style:normal"&gt;сборке мусора&lt;/i&gt; затраты памяти минимальны.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Получается, что ленивость – это эффективная стратегия вычислений для чистого языка. Более того, как было показано в начале, чистота является необходимым условием для ленивости. Круг замкнулся.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;7. Монады как связующее звено.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Итак, чистота языка привела нас к ленивости, но ленивость означает, что у нас нет фиксированного порядка вычислений. Во многих случаях это оправдано, но в случае операций с вводом-выводом нам нужна четкая последовательность определенных операций.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Во-первых, такая операция может быть представлена в чистом языке только как отложенное вычисление &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;x&lt;/span&gt;. Предположим, что есть следующая операция &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt;. Для создания последовательности из этих вычислений естественным будет формирование некоторой функции от двух аргументов, где первым аргументом будет первое вычисление, а вторым – последующее: &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="mso-tab-count:1"&gt;                &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;f&lt;/span&gt; :: &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt; -&gt; ?&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Чтобы сохранить оба вычисления мы должны вернуть новое. Тогда можем вернуть либо &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt;, либо &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt;. Выберем второе. Так мы получили функцию “&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;then&lt;/span&gt;”:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="mso-tab-count:1"&gt;                &lt;/span&gt;(&gt;&gt;): &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt; -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;В общем случае второе вычисление может зависеть от результата первого вычисления. Приходим к функции “&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;bind&lt;/span&gt;”:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="mso-tab-count:1"&gt;                &lt;/span&gt;(&gt;&gt;=): &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; -&gt; (&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;y&lt;/span&gt;) -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;my&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Получается, что мы можем соединять вычисления друг с другом, оставаясь всегда в рамках того же самого типа вычислений. Это важно. При этом конструируем каждый раз новое вычисление.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Иногда нам нужно вычисление, которое только и делает, что просто вычисляет заданное фиксированное значение &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;x&lt;/span&gt;. Для этого вводится функция “&lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;return&lt;/span&gt;”:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="mso-tab-count:1"&gt;                &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;return&lt;/span&gt;: &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt; -&gt; &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;m&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;x&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Так мы пришли к более полному определению типо-класса монад.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;8. Ленивость монад.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Хотя монады могут имитировать получение побочного эффекта, они остаются чистыми, будучи отложенными вычислениями. Более того, они остаются ленивыми. Было бы ошибкой думать, что монады задают строгий порядок вычислений. В них действительно содержится информация о порядке применения вычислений, но каждая монада вольна трактовать его по-своему. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;Например, монада &lt;span lang="EN-US" style="mso-ansi-language: EN-US"&gt;IO&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;опирается на этот порядок и производит операции ввода-вывода строго последовательно. Но так поступают не все монады. Например, монада &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Maybe&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;может просто проигнорировать оставшуюся часть вычислений, а монада &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;Backwards&lt;/span&gt;&lt;span lang="EN-US"&gt; &lt;/span&gt;&lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;State&lt;/span&gt;, вообще, распространяет состояние в порядке обратном следованию! Такое возможно только в ленивом языке.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;strong&gt;Итог.&lt;/strong&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Получается, что чистота, ленивость и монады взаимосвязаны. Последние два являются следствием чистоты. Но это не значит, что ленивость и монады характерны только для Хаскеля. Частично они присутствует и в других языках. Например, монады есть в &lt;span lang="EN-US" style="mso-ansi-language:EN-US"&gt;F&lt;/span&gt;#. Это – совершенно другой язык смешанного типа с энергичной стратегией вычислений. Но наиболее красиво и целостно это все выглядит именно в Хаскеле.&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8641042312683198534-5523379217567170057?l=dsorokin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsorokin.blogspot.com/feeds/5523379217567170057/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dsorokin.blogspot.com/2009/12/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5523379217567170057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8641042312683198534/posts/default/5523379217567170057'/><link rel='alternate' type='text/html' href='http://dsorokin.blogspot.com/2009/12/blog-post.html' title='Следствия чистоты языка'/><author><name>dsorokin</name><uri>http://www.blogger.com/profile/12343205630861084400</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
