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

Ярлыки:

четверг, Март 24, 2005
Dolphin Smalltalk BETA
В середине апреля начинается фаза закрытого бета-тестирования средства разработки Dolphin Smalltalk 6.0 от Object Arts.
вторник, Март 15, 2005
A Seaside Vacation
A Seaside Vacation на xprogramming.com - где Chet Hendrickson рассказывает, как создать web-приложение для Bowling Game на основе Seaside.
Dynamic typing

Никто не мог бы сказать на эту тему лучше, чем Martin Fowler:

Мне очень не хотелось вносить свой вклад в спор о статической и динамической типизации в языках программирования. Это одна из тех эмоциональных тем, где люди предпочитают спорить, нежели чем слушать. Но так как меня несколько раз спрашивали об этом, я поделюсь своим опытом. Я не пытаюсь убедить кого-то или что-то, но я надеюсь кто-нибудь найдет пищу для размышлений благодаря этому.
Когда я первый раз осознал различие, я быстро убедился в преимуществах статической типизации. Мой опыт в программировании начался с языков Basic и Fortran IV - где типизация была ограничена. Потом я перешел к Pascal и он быстро стал языком, с которым я получал от программирования удовольствие.
Когда я познакомился с объектами, я работал одновременно с C++ и Smalltalk. В течение некоторого времени я рассматривал динамическую типизацию Smalltalk-а как недостаток - небольшая плата за восхитительную в остальном продуктивность платформы.
Я реально начал задаваться этим вопросом, когда был вовлечен в средние по размеру проекты на Smalltalk. Общий довод в пользу статических типов заключается в том, что они отлавливают ошибки, которые по-другому найти сложно. Но я обнаружил, что, при наличии SelfTestingCode, большинство ошибок, которые имелись бы со статическими типами, настолько же просто будут найдены тестами. А так как тесты находят намного больше, чем ошибки типизации, они нужны и в статически и в динамически типизированных языках, так что наличие статической типизации дает немного преимуществ.
Также было интересно увидеть, что другие сделали открытие в этом же направлении. Robert Martin и Bruce Eckel обнаружили то же самое при переходе от C++ к Python.
Однажды я пытался проанализировать некоторый хорошо написанный код на Ruby. Я обнаружил, что недостаток информации о типах параметров делает жизнь сложнее - я продолжал говорить себе "что именно мы здесь имеем?". Это не было такой проблемой в Smalltalk по двум причинам: превосходная среда позволяет просто открыть отладчик и посмотреть что ты имеешь (Прим.перев.: в действительности отладчик применяется достаточно редко, обычно используется браузер и команды Senders/Implementors для просмотра кто посылает/реализует сообщение), и, во-вторых, общепринятое соглашение об именовании параметров по их типам (это имеет смысл, так как Smalltalk имеет параметры с ключевыми словами, а не позиционные, так что ключевое слово уже объясняет роль, которую играет параметр).
Другая область, где статическая типизация нелишняя, это то, что она делает среды разработки намного более полезными. Открытием здесь (и во многих других вещах) была IntelliJ. С IDE, подобной этой, я действительно чувствовал, что система типов помогает мне. Даже простые вещи вроде авто-завершения в большей степени возможны благодаря статическим типам, и ведущие IDE могут намного больше, чем это.
Но, несмотря на все это, есть нечто особенно привлекательное в программировании на языках вроде Smalltalk и Ruby - и я думаю это в значительной мере определяется динамической типизацией. Беседуя на Camp 4 Coffee с Bruce Eckel мы оба согласились, что одной из самых разочаровывающих вещей в спорах о статической/динамической типизации, является то, что очень тяжело выразить словами преимущества работы с динамически типизированным языком. Каким-то образом все протекает лучше, когда ты программируешь в такой среде, даже когда я использую Ruby в Emacs-е вместо IntelliJ (Smalltalk, конечно, обладает и языком и восхитительной средой разработки).
Я подозреваю, что выразительность языка позволяет такие вещи, как DomainSpecificLanguage в самом языке. Работая с языками вроде Java и C# я всегда чувствую необходимость бегло просмотреть тект, чтобы понять что происходит.
Но, какова бы ни была причина, этот непрерывный поток (flow) приводит к большей радости от программирования - даже в худшей среде. Может показаться что это имеет небольшое значение, кого волнует испытывают ли программисты радость? Но меня волнует, потому что я действительно получаю удовольствие от программирования. Мне нравится делать вещи быстро без пустого трепа между процессом мышления и запуском кода. Для меня это удовольствие от Smalltalk и Ruby, и почему я перехожу к ним в моих персональных проектах. И в радости имеется ценность для бизнеса - в конце концов мотивация это главный фактор продуктивности программиста.

Как можно заметить, Martin Fowler отметил две особенности Smalltalk, отличающие его в лучшую сторону от других динамических языков - великолепная среда разработки и самодокументирующийся код, не ухудшающий читабельность даже при отсутствии статического указания типов.

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

Последние сообщения
:: Smalltalk и Все-Все-Все: Белка-Рыба наносит ответн...
:: Smalltalk и Все-Все-Все
:: [Squeak] Новый сайт Squeakland
:: [Squeak] Squeak для iPhone
:: [Squeak] SqueakDBX
:: [Squeak] Monticello 2
:: [GST] GNU Smalltalk 3.0.4 release
:: MagLev - Gemstone for Ruby
:: [Squeak] JSqueak, Potato
:: [Squeak] WxSqueak 0.5

Архив
Предыдущие новости / Декабрь 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