Alterator/objects: различия между версиями

Материал из ALT Linux Wiki
м (wiki-formatting, +category)
 
(не показана 1 промежуточная версия 1 участника)
Строка 1: Строка 1:
[[Category:Sisyphus]]
[[Category:Sisyphus]]
[[category:alterator|objects]]
{{MovedFromFreesourceInfo|AltLinux/Sisyphus/Alterator/objects}}
{{MovedFromFreesourceInfo|AltLinux/Sisyphus/Alterator/objects}}


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


<source lang="scheme">
<pre lang="scheme">
(define (make+ x)
(define (make+ x)
     (lambda (y) (+ x y)))
     (lambda (y) (+ x y)))
Строка 22: Строка 23:


(define +five (make+ 5))
(define +five (make+ 5))
(+five 5) ==> вернёт десять.</source>
(+five 5) ==> вернёт десять.
</pre>


Здесь функция +three, появившись, запомнила, что x — это 3, и при всех последующих запусках будет работать именно с этим значением, даже если мы явно зададим x значение 10.
Здесь функция +three, появившись, запомнила, что x — это 3, и при всех последующих запусках будет работать именно с этим значением, даже если мы явно зададим x значение 10.
Строка 28: Строка 30:
Итак, у нас есть «память», а если есть «память», значит можно сделать функции с состоянием, иначе говоря объекты.
Итак, у нас есть «память», а если есть «память», значит можно сделать функции с состоянием, иначе говоря объекты.
Вот например простейший объект, изображающий точку на плоскости:
Вот например простейший объект, изображающий точку на плоскости:
<source lang="scheme">
<pre lang="scheme">
(define (make-point-2d x y)
(define (make-point-2d x y)
                   (lambda (op . value)
                   (lambda (op . value)
Строка 40: Строка 42:
(point 'get-x) ==> вернёт 3
(point 'get-x) ==> вернёт 3
(point 'set-x 10) ; запомнит в своём окружении новое значение x
(point 'set-x 10) ; запомнит в своём окружении новое значение x
(point 'get-x) ==> вернёт 10</source>
(point 'get-x) ==> вернёт 10
</pre>


Вот теперь ясно видно, что имея замечательное свойство «памяти», можно создавать то, что в других языках программирования называют объектами, то есть совокупность данных и методов, работающих с этими данными. То, как именно строить объекты — никто вас не ограничивает, поэтому существует множество вариантов объектных систем для ''Scheme''. Система объектов Alterator похожа на объектную систему '''T'''.
Вот теперь ясно видно, что имея замечательное свойство «памяти», можно создавать то, что в других языках программирования называют объектами, то есть совокупность данных и методов, работающих с этими данными. То, как именно строить объекты — никто вас не ограничивает, поэтому существует множество вариантов объектных систем для ''Scheme''. Система объектов Alterator похожа на объектную систему '''T'''.
Строка 179: Строка 182:


Какие же объекты применяются в самом alterator?
Какие же объекты применяются в самом alterator?
* '''<attribute>'''  - применяется в описании интерфейса, олицетворяет собой атрибут с заданным значением, позволяет делать записи вида: ''(vbox (with 10) (height 20)) '' .Это полезно когда надо задать в атрибуте сразу несколько значений.
* {{term|<attribute>}} — применяется в описании интерфейса, олицетворяет собой атрибут с заданным значением, позволяет делать записи вида: {{term|(vbox (with 10) (height 20))}}. Это полезно когда надо задать в атрибуте сразу несколько значений.
* '''<container>'''  - контейнер с вложенными контейнерами и свойствами. Превращается в тот или иной виджет в интерфейсе.
* {{term|<container>}} — контейнер с вложенными контейнерами и свойствами. Превращается в тот или иной виджет в интерфейсе.
Также применяются объекты для нативных бакендов alterator.
Также применяются объекты для нативных бэкендов alterator.


....
.

Текущая версия от 20:41, 23 июня 2016

Freesource-logo.png Blue Glass Arrow.svg MediaWiki logo.png
Эта страница была перемещена с freesource.info.
Эта страница наверняка требует чистки и улучшения — смело правьте разметку и ссылки.
Просьба по окончанию убрать этот шаблон со страницы.


Объектная система alterator

В текущей нестабильной ветке alterator произошло большое изменение: там внедрены объекты. Зачем это нужно? Нужно это для того, чтобы поддерживать код alterator было легче, чтобы убрать старые хаки, уменьшить количество cond и case, затрудняющих чтение. Что это такое? Читайте далее...

Объекты — это замыкания для бедных

Эта цитата принадлежит Norman Adams и сейчас вы убедитесь насколько это верно ;)

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

(define (make+ x)
     (lambda (y) (+ x y)))

(define x 10)

(define +three (make+ 3))
(+three 5) ==> вернёт восемь.

(define +five (make+ 5))
(+five 5) ==> вернёт десять.

Здесь функция +three, появившись, запомнила, что x — это 3, и при всех последующих запусках будет работать именно с этим значением, даже если мы явно зададим x значение 10.

Итак, у нас есть «память», а если есть «память», значит можно сделать функции с состоянием, иначе говоря объекты. Вот например простейший объект, изображающий точку на плоскости:

(define (make-point-2d x y)
                   (lambda (op . value)
                        (case op
                             ((get-x) x)
                             ((get-y) y)
                             ((set-x) (set! x (car value)))
                             ((set-y) (set! y (car value))))))

(define point (make-point-2d 3 4))
(point 'get-x) ==> вернёт 3
(point 'set-x 10) ; запомнит в своём окружении новое значение x
(point 'get-x) ==> вернёт 10

Вот теперь ясно видно, что имея замечательное свойство «памяти», можно создавать то, что в других языках программирования называют объектами, то есть совокупность данных и методов, работающих с этими данными. То, как именно строить объекты — никто вас не ограничивает, поэтому существует множество вариантов объектных систем для Scheme. Система объектов Alterator похожа на объектную систему T.

Методы или сообщения

Работают с объектами, как правило, одним из двумя способами: явно обращаясь в методам или данным или передавая объекту сообщение. Эти способы совершенно равноценны. Просто в одном языке удобнее вызвать метод:

object.method(params)

В другом, послать объекту сообщение с желанием выполнить метод:

(send-message object 'method params)

Как вы догадываетесь, в LISP принято использовать второй способ.

Простые объекты

На вводимые дальше объекты можно смотреть как на существенно улучшенные функции. С одной стороны объект будет содержать в себе собственно тело функции, с другой стороны у него будет "вторая дверь", через которую можно будет вызывать определённые в объекте операции. То есть мы получаем объект "функция с рычагами".

Общий формат процедуры создания объектов следующий:

(object <proc> <methods>)

<proc> - выражение которое будет исполняться при каждом вызове объекта (дверь #1). <methods> - набор определённых операций (дверь #2). Операций может и не быть. Вырожденный случай (есть только дверь #1):

(object (lambda args #f))

Это тоже самое что и просто процедура:

(lambda args #f)

Методы описываются в следующем формате:

((name self args) body)

Здесь:

  • name - имя операции.
  • self - ссылка на сам объект. Очень полезно когда из одного метода хочется вызвать другой метод того же объекта.
  • args - необязательные дополнительные параметры метода.
  • body - тело метода.

Операции

Как же войти во вторую дверь? Для этого существуют так называемые операции. Создаётся операция при помощи инструкции (operation имя) Можно сразу создать и определить операцию (define-operation имя). Последняя конструкция полностью аналогична операции (define имя (operation имя))

Создадим объект точки на плоскости:

(define (make-point-2d x y)
     (object
           'called
          ((get-x self) x)
          ((get-y self) y)
          ((set-x self value) (set! x value))
          ((set-y self value) (set! y value))))

(define-operation get-x)
(define-operation set-x)

(define point (make-point-2d 3 4))
(point) ==> при исполнении вернёт символ 'called как и просили
(get-x point) ==> вернёт 3
(set-x point 10) ==> запомнится новое значение x
(get-x point) ==> вернёт 10

У процедуры operation есть ещё один необязательный параметр - действие по умолчанию. В случае если та или иная операция не была обнаружена, то будет запущено действие по-умолчанию. Если действие по умолчанию не определено, а требуемая операция в объекте не обнаружена, то будет выдано сообщение об ошибке и исполнение кода прервётся. Небольшой пример:

(define-operation op 333) ; операция с действием по-умолчанию - вернуть число 333
(define-operation op2); операция без действия по-умолчанию
(op (object #f ((op self) 444))) ==> вернёт 444, ибо операция обнаружена и исполнена
(op (object #f ((op2 self) 555))) ==> вернёт 333, ибо операция не обнаружена, но есть действие по-умолчанию.
(op2 5) ==> ошибка! операции не найдено
(op 5) ==> вернёт 333.

Это неприметное на первый взгляд действие по-умолчанию позволяет творить чудеса. Вот так например можно сделать предикат различающий только интересующие нас объекты

(define-operation obj? #f)
(define obj (object #f ((obj? self) #t))))
(obj? obj) ==> вернёт #t
(obj? '(1 2 3)) ==> вернёт #f

Возможны и другие фокусы ....

Составные объекты

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

Для комбинирования объектов используется процедура join. В результате применения join объектам (object proc1 method11 method12 ...) (object proc2 method21 method22 ...) Получится объект (object proc1 method11 method12 ... method21 method22 ... ).

Создадим объект точки в пространстве:

(define (make-point-3d x y z)
   (join
        (make-point-2d x y)
        (object
            #f
           ((get-z self) z)
           ((set-z self value) (set! z value)))))

(define-operation get-x)
(define-operation get-z)

(define point (make-point-3d 3 4 5))
(get-x point) ==> вернёт 3
(get-z point) ==> вернёт 5
(point) ==> вернёт 'called как если бы это был вызван point-2d.
(join 1 (make-point-3d 2 3 4)) ==> а можно делать и такие вот "соединения", при вызове этот объект будет возвращать 1, а по операциям будет совпадать с объектом типа point3d.

Все примеры выше не являются по сути вызываемыми объекты. Где же могут быть полезны настоящие двудверные функции-объекты?

Возьмём пример из самого alterator.

Создадим объект "пустой атрибут". Если у него спросить имя атрибута, то он его вернёт, а любые попытки вызова интерпретируются как пожелание задать данному атрибуту значение и в результате возвращается объект "атрибут со значением". Последний также воспринимает сообщения на предмет получения его имени и значения, а все попытки вызова интерпретирует как пожелание добавить ещё одно значение, то есть в результате возвращается атрибут с расширенным значением. Пример:

width --> это пустой атрибут
(name-of width) ==> возвращает имя атрибута, то есть width
(width 10) ==> возвращает "атрибут со значением" 10
(name-of (width 10))  ==> имя width
(value-of (width 10)) ==> значение 10
((width 10) 20) ==> "атрибут со значением" (10 20).

Такое поведение очень удобно использовать в описании интерфейса

(label "aaa" width 20)
(some-widget (dimensions 10 50) color 'blue ...)

Описываются эти необычные объекты очень и очень просто:

(define (make-empty-attribute attr-name)
  (set-instance!
    '<empty-attribute>
    (object (lambda args
              (apply make-attribute attr-name args))
            ((name-of self) attr-name))))

;create attribute with value
(define (make-attribute attr-name . attr-value)
  (set-instance!
    '<attribute>
    (object
      (lambda args
        (apply make-attribute attr-name (append attr-value args)))
      ((name-of self) attr-name)
      ((value-of self) attr-value))))

Ну вот и всё. Объектная система простая - как и всё в Scheme.

Объекты в alterator

Какие же объекты применяются в самом alterator?

  • <attribute> — применяется в описании интерфейса, олицетворяет собой атрибут с заданным значением, позволяет делать записи вида: (vbox (with 10) (height 20)). Это полезно когда надо задать в атрибуте сразу несколько значений.
  • <container> — контейнер с вложенными контейнерами и свойствами. Превращается в тот или иной виджет в интерфейсе.

Также применяются объекты для нативных бэкендов alterator.

….