Smalltalk по-русски
четверг, Март 31, 2005
[VW] Знаете ли Вы, что такое pragma?
Что такое прагма

При разработке фреймворков зачастую требуется отобрать определённые методы для обработки или использования. Простейщий пример это SUnit. SUnit считает методы начинающиеся с "test" методами-тестами, которые нужно выполнить при тестировании. Smalltalk обладает огромными возможностями рефлексии. По этому, получая информацию о программе, можно решить данную задачу, кодируя метаинформацию в именах методов, как это делает SUnit, или более продвинутыми способами. Однако, при наличии потребности, более удобно иметь обобщенное средство, а не изобретать каждый раз очередной "велосипед". В VisualWorks таким средством является механизм прагм.

Прагмы (pragmas) служат для добавления дополнительной информации к методу. API позволяет запросить объекты представляющие собой прагмы определённого метода, найти методы содержащие прагмы с некоторым именем - селектором, отслеживать добавление/удаление таких методов. Подобные средства вы можете найти и в других языках/платформах, например, в Java (начиная с релиза 5) и в .Net. В Java это аннотации [методов], в .Net - атрибуты [методов]. Не обделены и другие диалекты ST. Для подобных целей существует механизм, называемый так-же - прагмы, в IBM Smalltalk/Visual Age. Василий Байков (Vassili Bykov) так же реализовал прагмы а-ля VW для Squeak (реализация доступна по запросу). Аннотации с синтаксисом а-ля VW широко используются в Tweak для объявления обработчиков событий. Аналог прагм - "аннотации" - существует и в Gnu Smalltalk начиная с версии 2.1.

Долгая дорога к прагмам

Историю возникновения прагм можно начать со Smalltalk-80 (а может и с более ранних времён). Тогда существовала специальная конструкция для вызова примитивов - методов реализованных в виртуальной машине (ВМ). Конструкция имела вид:

<primitive: N>
N - номер примитива в ВМ. В ObjectWorks 2.5 этот синтаксис был расширен:
<primitive: N errorCode: errorCodeTempVar>
Называлась эта конструкция именно прагма, но к обсуждаемым в статье прагмам не имеет прямого отношения. Компилятор просто генерировал специальный байт-код для вызова примитива, но ни о какой метаинформации речи не шло.

В конце 1989 г. вышел Smalltalk-80 version 2.5, содержавший среди нововведений C programmer's Object Kit (CPOK, сейчас называется DLLCC). Для определения функций и типов данных на С там использовался (и сейчас используется) прагмаподобный синтаксис:

<C: RETCODE SQLAllocConnect(HENV henv, HDBC * phdbc)>
Для компиляции подобных выражений требуется компилятор, содержащийся в парселе DLLCC.

В VisualWorks 1.0 (1992г.) появилась еще одна прагма для пометки методов, в которых хранились ресурсы - спецификации GUI и меню:

<resource: #symbol>
Где #symbol это #canvas, #image и т.д. В данное время это "обычная" прагма.

С необходимостью искать определённые методы столкнулись программисты и при разработке исключений. Исключения появились в начале 1989г. в Smalltalk-80 version 2.4. При возникновении исключения необходимо найти контекст активации содержащий обработчик. В далёком 1989г. проблема была решена просто - при добавлении метода в словарь методов класса вызывался хук #validateMethod:forSelector: (существующий в VW и до ныне). Если добавлялся метод с определённым именем, например, #valueNowOrOnUnwindDo:, то он помечался соответсвующей пометкой. Чтобы не привязыватся к именам селекторов Элиот Миренда (Eliot Miranda) предложил использовать для этих целей новую прагму:

<exception: #unwind>
А Стив Дэхл (Steve Dahl) обобщил этот механизм. Теперь любые сообщения с аргументами-литералами сохраняются в методах, а подобные методы можно найти. Если вам интересно как это устроено "внизу", то посмотрите классы AnnotatedMethod и его подкласс MarkedMethod.

Уже в VisualWorks 3.0 (начало 1998г.) прагмы использовались во всю. Например, меню там компоновались "на лету" из методов содержащих специальную прагму. При добавлении или удалениии метода с прагмой соответсвующий пункт тут же появлялся или исчезал в меню.

Интерфейс работы с прагмами

Для того, что бы использовать прагмы список допустимых прагм желательно объявить явно. Для этого используется метод на стороне класса, возвращающий коллекцию допустимых селекторов прагм. Этот метод должен быть аннотирован прагмой #pragmas:, которая указывает область применения данных прагм - на стороне класса или на стороне экземпляра. Для указания, что объявляемые прагмы используются на стороне класса служит аргумент #class, на стороне экземпляра - #instance. Пример:

BlockClosure class>>exceptionPragmas
 <pragmas: #instance>

 ^#(#exception:)
Если прагмы могут использоваться и в коде на стороне класса и на стороне экземпляра, то нужно аннотировать метод двумя прагмами. Например:
Object class>>resourceMethodPragmas
 <pragmas: #instance>
 <pragmas: #class>

 ^#(#resource:)
При компиляции метода с необъявленной прагмой будет выведено предупреждение (которое можно проигнорировать).

Прагмы, используемые в методах, представлены экземплярами класса Pragma. Для поиска экземпляров прагм используются методы на стороне класса Pragma в протоколе 'finding'. Метод с селектором #allInMethod: возвращает коллекцию экземпляров прагм определённых в данном методе (объекте класса CompiledMethod). Существует и ряд методов позволяющих найти аннотации в иерархиях классов. Например, метод с селектором allNamed: aSymbol in: aClass возвращает все экземпляры прагм с указанным селектором в одном определённом классе. Метод с селектором allNamed: aSymbol from: subClass to: superClass возвращает коллекцию экземпляров прагм найденных в методах, которые определены в иерархии классов от подкласса subClass до базового класса superClass.

Для работы с экземплярами прагм используется ряд сообщений. Для получения колличества аргументов используется сообщение #numArgs; argumentAt: anInteger возвращает аргумент прагмы с порядковым номером anInteger; в ответ на сообщение #arguments экземпляр прагмы возвращает коллекцию аргументов; сообщение #keyword служит для получения селектора прагмы; сообщение #message, посланное экземпляру прагмы, вернёт объект класса Message с селектором прагмы и аргументом прагмы.

Экземпляр прагмы также позволяет получить аргументы не через индекс, а напрямую. Для этого служит сообщение #withArgumentsDo:. Это сообщение нужно послать экземпляру прагмы с одним параметром - блоком. Этот блок будет вызван с аргументами прагмы, соответсвенно, колличество параметров блока и агрументов прагмы должны совпадать. Пример:

VisualLauncherToolDock class>>componentSpecCollection
 | specs |
 specs := OrderedCollection new.
 (Pragma allNamed: #component:class:spec: in: self sortedByArgument: 1) do:
  [:pragma | | spec |
  spec := SubCanvasSpec new.
  pragma withArgumentsDo:
   [:order :classBinding :specSelector |
   spec
    clientKey: pragma selector;
    majorKey: classBinding;
    minorKey: specSelector].
  specs add: spec].
 specs isEmpty ifFalse: [self setLayoutsIn: specs].
 ^SpecCollection new collection: specs
Вначале отбираются экземпляры прагмы с селектором #component:class:spec: (три параметра) при помощи сообщения #allNamed:in:, а, затем, аргументы найденных прагм передаются в блок, имеющий три параметра:
[:order :classBinding :specSelector |
spec
    clientKey: pragma selector;
    majorKey: classBinding;
    minorKey: specSelector].

Применение прагм

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

Василий Байков (Vassili Bykov) разработал набор переиспользуемых инструментов. При компоновке различных модулей, что бы избежать написания большого числа методов, которые просто делегируют выполнение другим объектам, используется механизм делегирования построенный на прагмах. Этот механизм определён в классе CompositeToolModule от которого наследуются композитные инструменты.

Механизм очень простой. Для задания делегируемых сообщений используется ряд прагм: #delegate:, #delegate:as:, #relay, #relay:as:. Первым аргументом прагм явлется селектор пересылаемого сообщения. Второй аргумент as: используется если пересылаемое сообщение нужно "переименовать". Этими прагмами могут быть помечены унарные методы (методы без параметров). Сообщения, попадающие в #doesNotUnderstend: пересылаются объекту, который будет возвращён помеченным унарным методом. Таким образом можно пересылать различные сообщения разным объектам. Прагмы #forward:* отличаются от прагм #relay:* только тем, какой результат будет возвращен после пересылки. Так, использование прагмы #forward: приведёт к пересылке сообщения новому получателю и к возврату результата "нового" сообщения, а при использовании прагмы #relay: после пересылки сообщения новому получателю результатом возврата будет исходный (делегирующий) объект.

Примеры таких объявлений можно посмотреть в подклассах класса CompositeToolModule. Вот одно из объявлений:

itemModule
    <relay: #itemDisplayStringSelector: as: #displayStringSelector:>
    <relay: #itemDisplayStringBlock: as: #displayStringBlock:>
    <relay: #itemIconSelector: as: #iconSelector:>
    <relay: #itemIconBlock: as: #iconBlock:>
    <delegate: #selection>
    <delegate: #selectionHolder>

    ^itemModule

Ярлыки:

Comments:
Интересно что в VW 7.0 класса Pragma нет. В 7.1 уже есть.
 
Жалко что VW не поддерживает Traits, тогда бы модуля можно было не только прагмами компоновать.
 
Наверное для динамички формируемых меню можно было бы просто внутри методов вызывать какое-то заранее известное сообщение. И комановать меню из методов которые посылают это сообщение. Анологично можно было поступить для ресурсов (image, windowSpec). Но зачем-то ведь решили использовать прагмы :)
 
Можно и вызывать зараннее известное сообщение (см. #notYetImplemented в VW). Однако прагма предлагает обобщенное решение.
Кстати, я забыл указать, что методы, в которых используются прагмы, можно найти через "Browse -> Senders of Selector". Попробуй, например, найти ссылки на "resource:". А если выделить метод в броузере, то прагма будет в списке методов к которым можно искать отправителей. То есть, выделив метод с прагмой нужно найти отправителей с интересуещей прагмой - откроется список методов, в которых используется данная прагма.
 
Отправить комментарий

<< Home

Популярные статьи
:: Smalltalk?!
:: Почему Smalltalk?
:: Great Leap Forward from Java to Smalltalk

Последние сообщения
:: Dolphin Smalltalk BETA
:: A Seaside Vacation
:: Dynamic typing
:: [Squeak] 64-bit VM
:: Анонсы событий от ESUG и STIC на 2005-й год
:: Bruce Eckel о языках для обучения программированию...
:: Alan Kay о Smalltalk
:: Don Box о языках для обученя программированию
:: The Smalltalk Report. 1996
:: [Squeak] Самосформировалась команда, которая будет...

Архив
Предыдущие новости / Декабрь 2004 / Январь 2005 / Февраль 2005 / Март 2005 / Апрель 2005 / Май 2005 / Июнь 2005 / Июль 2005 / Август 2005 / Сентябрь 2005 / Октябрь 2005 / Ноябрь 2005 / Декабрь 2005 / Январь 2006 / Февраль 2006 / Март 2006 / Апрель 2006 / Май 2006 / Июнь 2006 / Июль 2006 / Сентябрь 2006 / Октябрь 2006 / Ноябрь 2006 / Декабрь 2006 / Январь 2007 / Февраль 2007 / Март 2007 / Апрель 2007 / Май 2007 / Июнь 2007 / Август 2007 / Сентябрь 2007 / Ноябрь 2007 / Январь 2008 / Март 2008 / Май 2008 / Июнь 2008 / Июль 2008 / Август 2008 / Сентябрь 2008

Atom Feed
Smalltalk по-русски


Powered by Blogger