nflow v0.2.0
love5an — 30.01.2011 Полностью переработал модель распространения данных в neural-flowНе без помощи dmitry_vk в [email protected], надо сказать.
Библиотека стала больше походить на cells. Но, её интерфейс и dataflow-модель - намного проще и удобнее в использовании.
Теперь зависимости между нейронами вычисляются автоматически, динамически, и, кроме того, один нейрон может зависеть от нескольких других одновременно.
Что значит "автоматически"? Это значит, что, в отличие от предыдущей версии, теперь не нужно руками делать add-connection и remove-connection. Нейрон сам вычислит, с кем он связан и соответствующим образом распространяет поток данных.
Что значит "динамически"? Это значит, что связи между нейронами создаются и изменяются во время выполнения программы, на основе взаимодействия нейронов, а не на основе каких-либо статически заданных формул. Короче, как и у настоящих нейронов.
А связи между настоящими нейронами зависят от множества факторов - от расположения нейрона, от количества алкоголя в крови, да и даже от фазы луны.
Кстати, о фазе луны. Вот сразу пример :)
(defparameter *input* (neuron ()
nil))
(defparameter *first* (neuron ()
(neuron-value *input*)
:first))
(defparameter *second* (neuron ()
(neuron-value *input*)
:second))
(defparameter *output* (neuron ()
(if (evenp (floor (get-universal-time) ;;количество секунд с 1 января 1900 года
(* 60 60 24 29.5 1/2))) ;;количество секунд в лунном полуцикле
(neuron-value *second*)
(neuron-value *first*))))
(defun query-lunar-phase ()
(update-neuron *input*)
(neuron-value *output*))
В первую половину лунного цикла нейрон *output* будет узнавать значение лунной фазы из нейрона *first*, а во вторую половину - из *second*. Ну, довольно очевидно, что на сегодняшний день он будет хранить значение :second :)
Макрос neuron это просто удобная форма записи (make-instance 'neuron ...)
Выглядит он так:
(defmacro neuron ((&optional (neuron-class 'neuron) (value-var (gensym)) &rest args)
&body body)
(check-type value-var symbol)
`(make-instance ',neuron-class
:function (lambda (,value-var) (declare (ignorable ,value-var)) ,@body)
,@args))
Как видно, в функции query-lunar-phase мы не указываем явно, что нам нужно обновить именно нейрон *output*, который собственно и вычисляет фазу луны. Напротив, мы обновляем только нейрон *input*. И тем не менее, поток данных проходит весь путь от *input* к *output*.
Как это достигается? Очень просто - вызов функции neuron-value записывает нейрон, из которого мы эту функцию вызываем, в список зависимостей нейрона, к которому мы обращаемся.
(defun neuron-value (neuron)
(declare (type neuron neuron))
(when *current-neuron*
(pushnew *current-neuron* (slot-value neuron '%dependents) :test #'eq))
(slot-value neuron '%value))
Таким образом, связи между нейронами зависят исключительно от того, куда нейрон дотягивается "дендритами" во время выполнения программы. Для вышеописанного примера, на сегодняшнее число, связи будут выглядеть так:
Прослеживается аналогия с монадами - нейрон, как и монада, это некое звено в цепочке вычислений. Отличие в том, что функция нейрона выполняет роли как bind, так и return; впрочем, я думаю позже ввести "синапсы" - обобщенные функции, которые бы обрабатывали значение, полученное из нейрона, и значение, возвращаемое самим функтором.
Важное отличие от традиционного FRP(functional reactive programming) состоит в том, что в моей библиотеке используется data-driven(push-based, eager) модель, в противоположность demand-driven(pull-based, lazy) моделям, которые обычно используются в FRP-фреймворках.
На практике это означает то, что поток данных инициируется каким-либо событием, например, изменением слота, хранящего текущее значение нейрона, или вызовом update-neuron, в противоположность традиционному FRP, где вычисления начинаются при попытке узнать значение слота.
Напоследок, еще один пример:
;;В объектах классов, являющихся экземплярами метакласса dataflow-class
;; все слоты являются нейронами
;; При этом, мы можем продолжать с ними работать как с обычными слотами.
(defclass textbox (dataflow-object)
((text :initform "" :accessor textbox-text))
(:metaclass dataflow-class))
(defclass checkbox (dataflow-object)
((checked :initform nil :accessor checkbox-checked))
(:metaclass dataflow-class))
(defun event-example ()
(let* ((textbox (make-instance 'textbox))
(checkbox (make-instance 'checkbox)))
;;bindf это обобщенная функция, устанавливающая нейрону или слоту(как здесь)
;; новую функцию-обработчик, или же устанавливающая слоту новый нейрон.
(bindf (lambda (old-value)
(declare (ignore old-value))
(if (checkbox-checked checkbox)
"Checked"
"Unchecked"))
textbox
:slot 'text)
;;просто некий нейрон, который будет нас оповещать об изменениях
;; слота text у textbox'а
(let ((observer (neuron ()
(format t "Text changed. New value: ~s"
(textbox-text textbox)))))
(declare (ignorable observer))
checkbox)))
;;При изменении слота checked, слот textbox'a также будет изменяться -
;; устанавливаться в "Checked" или "Unchecked" соответственно.
;; А при изменении самого слота text, этим, или
;; другим способом(например через (setf slot-value)),
;; об изменении нас будет оповещать нейрон observer
|
</> |