Незнакомый Smalltalk

Ю.А. Кирютенко и В.А. Савельев

Введение к разговору о языке Smalltalk

В качестве первого эпиграфа - типичный разговор российских программистов:
1-ый программист: "Что ты знаешь о языке Smalltalk?"
2-ой программист: "Ничего, я с ним не знаком!"

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

Особую роль в популярности этого подхода сыграла его тесная связь с интерфейсами пользователя (особенно графическими), а также включение элементов этой идеологии в популярные сегодня (на персональных компьютерах фирмы IBM) языки программирования C++ и Pascal with Objects фирмы Borland (эти языки некоторые авторы называют "гибридными"). Распространение мощных и доступных по цене персональных компьютеров дало техническую основу для их широкого распространения и применения в программисткой практике.

За последние пятилетие в США, Японии и Западной Европе значительно вырос интерес к "чистому" объектно-ориентированному языку программирования - языку Smalltalk. Вероятно самое первое издание, посвященное этому языку - августовский выпуск журнала "BYTE" в 1981 году, на обложке которого впервые появился воздушный шар с именем Smalltalk на борту, резво летящий от уединенного острова "общих исследований" к высотам популярности! Сегодня, число ежегодно издающихся книг по объектно-ориентированной методологии программирования и языку Smalltalk там переваливает за сотню. Только в России и теоретические исследования и программистская практика, связанные с этим направлением, широким кругам программистов практически не известны. Книги на русском языке, хотя бы едва-едва затрагивающие эти вопросы, можно пересчитать на пальцах одной руки.

В море англоязычных книг, посвященных объектно-ориентированной методологии программирования и языку Smalltalk, выделяется книга, первое издание которой получило  "литературный псевдоним" - Голубая Книга (Blue Book). В нашей статье речь пойдет о втором издании этой книги, книги написанной членами группы разработчиков системы Smalltalk-80 Адель Голдберг (Adele Goldberg, Parc Place System) и Дэвидом Робсоном (David Robson, Xerox PARC) "Smalltalk-80: The language, ed.2, Addison-Wesley, 1989", которую можно смело назвать "классикой программистской литературы". Сегодня одна из самых цитируемых книг в данной области, она, несмотря на прошедшие годы, до сих пор все еще остается, на наш взгляд, лучшей книгой об объектно-ориентированной парадигме программирования, о ее чистой и полной реализации в языке программирования Smalltalk-80 и о структуре и возможностях самого языка программирования.

Книга уникальна уже потому, что предлагает исчерпывающее описание объектно-ориентированной идеологии программирования (часть 1), содержит подробное описание реализации этих идей в объектно-ориентированном языке программирования Smalltalk-80 (часть 2), дает примеры его эффективного использования при создании объектов, имитирующих поведение моделей, управляемых событиями (часть 3). По своему содержанию книга интересна и для тех, кто хотел бы познакомиться только с объектно-ориентированной парадигмой программирования, и для тех, кто использует эту идеологию, независимо от того каким языком программирования - "чистым" или "нечистым" - он пользуется, и для тех, кто сегодня читает лекции и ведет практические занятия по современным языкам программирования в вузе, лицее или школе. Книга, при соответствующем уровне математической и программистской культуры, доступна учащимся старших классов средней школы и студентам младших курсов вуза, за исключением, может быть, третьей части, для понимания которой необходимо прослушать хотя бы небольшой курс по основам теории вероятности и математической статистики.

Но книга практически недоступна! Англоязычное издание, или перевод на ряд европейских языков, в России не купить и в библиотеках не найти. Ее перевод на русский язык, сделанный авторами этой статьи, которые по ходу перевода, разработали и читают курсы лекций по данной тематике на механико-математическом факультете Ростовского государственного университета, более года не находит в России издателя. Цель данной публикации - рассказать о том, что  у нас пока  мало известно, найти единомышленников, и, очень хочется надеяться, найти спонсоров и издателя Голубой Книги (пока почти что сказки) программирования!

Попытаемся, перелистывая страницы Голубой Книги, рассказать что же такое Smalltalk, и, одновременно, рассказать немного о содержании самой книги.


Smalltalk: Идеология системы

"Книга - это зеркало. Если в него смотрится обезьяна, то из него не может выглянуть лик апостола."

К. Лихтенберг

Система Smalltalk-80, начало создания которой формой Xerox из городка Пало Альто, Калифорния, США, восходит к 1970 году, основывается на идеях алгоритмического языка Simula (Симула), и на идеях Элана Кея (Alan Kay), который предложил создать однородную объектно-ориентированную систему программирования, основанную на малом числе взаимосвязанных понятий. Новые и основные понятия - это прежде всего пять слов, составляющие словарь объектно-ориентированного языка программирования: объект, сообщение, класс, экземпляр, метод.

Созданный на этой идеологии в начале восьмидесятых Smalltalk-80 характеризуется следующими чертами:

Именно управление сложностью - один из основных вкладов языка Smalltalk-80 в современное программирование!


Smalltalk: Структура системы

"Кто долго размышляет, тот всегда находит лучшее решение."

И.В. Гете

Объекты и сообщения

Объект - компонент системы. Объектами, например, являются: числа, строки символов, очереди, словари, прямоугольники, каталоги файлов, редакторы текстов, программы, компиляторы, вычислительные процессы, финансовые отчеты, визуальные представления информации ...

Каждый объект состоит из некоторой принадлежащей ему памяти и множества операций, таким образом объединяя в себе (инкапсулируя) и данные и операции по их обработке. Природа операций над объектом зависит от того, что представляет объект. Объекты, представляющие числа, используют арифметические функции. Объекты, представляющие структуры данных, хранят и обрабатывают информацию.

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

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

Классы и экземпляры

Класс описывает реализацию множества объектов, которые представляют подобные компоненты системы. Отдельные объекты, описываемые классом, называются его экземплярами. Именно класс описывает структуру памяти своих экземпляров, которая представляется множеством переменных, и то, как экземпляры выполняют посланные им сообщения, то есть описывают методы, определяющие как выполнить операцию, затребованную соответствующим сообщением.

Каждый класс имеет имя, которое описывает тип компонента системы, представляемый его экземплярами. Например, класс, экземпляры которого представляют последовательности символов, называется String (Строка); класс, экземпляры которого представляют положение на плоскости, называется Point (Точка); класс, экземпляры которого представляют прямоугольные области, называется Rectangle (Прямоугольник); класс, экземпляры которого представляют вычислительные процессы, называется Process (Процесс).

Каждая переменная в принадлежащей объекту памяти ссылается на некоторый объект, называемый значением этой переменной. Большинство таких переменных имеют имена. Объекты, к которым можно получить доступ из конкретного места программы, определяются через доступные имена переменных. Имя переменной - простой идентификатор, то есть последовательность букв и цифр, начинающаяся с буквы. Например,

   index              bin14Total
   initialIndex       HouseholdFinances
   textEditor         Rectangle
   bin14              IncomeReasons
Метод может сделать некоторые изменения в собственной памяти объекта и/или послать другие сообщения. Метод также определяет объект, который должен быть возвращен как значение вызвавшего метод сообщения. Определенным в классе методам доступны пять различных видов переменных. Эти виды переменных различаются по широте доступа к ним и по времени их существования в системе.

Существуют два вида частных переменных, доступных только одному объекту.

  1. Переменные экземпляра - существуют в течение всего времени жизни объекта.
  2. Временные переменные - создаются для некоторого действия и доступны только во время этого действия.
Переменные экземпляра представляют текущее состояние объекта. Временные переменные отражают промежуточное состояние, необходимое для выполнения некоторого действия. Временные переменные обычно связаны с единичным выполнением метода: они создаются, когда сообщение вызывает метод для выполнения, и уничтожаются, когда метод завершает свою работу возвращением значения.

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

  1. Переменные класса - доступны всем экземплярам данного класса.
  2. Глобальные переменные - доступны всем экземплярам всех классов (то есть всем объектам системы).
  3. Переменные пула - доступны экземплярам некоторого подмножества классов системы.
Большинство общих переменных в системе - либо переменные класса, либо глобальные переменные. Большинство глобальных переменных ссылаются на классы системы. Глобальные переменные используются для ссылок на объекты, которые не являются частями других объектов.

Имена общих переменных (в том числе и имена классов!) пишутся с прописной буквы, в то время как имена частных переменных - со строчной. Значение общей переменной не зависит от того, какой объект системы использует метод, в котором присутствует ее имя. Значение переменных экземпляра и временных переменных зависит от экземпляра, использующего метод.

Программирование в системе Smalltalk состоит из создания классов, экземпляров классов и последовательности сообщений, которыми обмениваются эти объекты!

Подклассы, суперклассы, иерархия классов

Структура класса, как она описывалась до сих пор, ничего не говорила о каких-либо пересечениях между классами.

Отсутствие пересечений между классами - существенное ограничение на разработку в объектно-ориентированной системы. Но два объекта системы могут быть в основном похожи, отличаясь некоторыми деталями. Например, упорядоченные и неупорядоченные наборы похожи в том, что они представляют группы элементов, в которые можно добавлять и из которых можно удалять элементы, но они различаются по способу доступа к элементам. Различие между подобными объектами может быть заметно внешне, например, по тому, что они умеют отвечать на различные сообщения. Различие может быть и чисто внутренним и проявляться в том, что в ответ на одни и те же сообщения похожие объекты выполняют различные методы. Если классам не разрешено пересекаться, то возможность такого частичного подобия не может быть предоставлена системой.

Способ обойти вышеуказанное ограничение - разрешить некоторое пересечения между классами. Мы называем такой подход наследованием. В языке Smalltalk наследование допускает такую ситуацию, при которой одному классу разрешается содержать в себе все экземпляры другого класса, которые ``наследуют'' от первого структуру его экземпляров и их поведение. В такой ситуации мы будем говорить, что один класс является подклассом другого класса, который для первого является суперклассом.

В языке Smalltalk программист всегда создает новый класс как подкласс уже существующего класса. Системный класс Object (Объект) описывает общие свойства всех объектов системы, поэтому любой класс системы является как минимум подклассом класса Object. Описание класса явно указывает его суперкласс, и то, чем его экземпляры отличаются от экземпляров суперкласса. Подкласс тоже класс, следовательно, он сам может иметь подклассы. Каждый класс имеет один суперкласс, в то время как несколько классов могут быть подклассами одного и того же суперкласса; таким образом, структура классов образует дерево, которое определяет иерархию классов. Класс связан с последовательностью классов, от которых он наследует переменные и методы. Эта цепочка наследования начинается с его непосредственного суперкласса и заканчивается классом Object. Object - единственный корневой класс системы; он один не имеет суперкласса.

Приведем некоторую часть иерархии классов системы Smalltalk-80 (отступ обозначает подкласс).


Object                                  Stream
   Magnitude                               PositionabIe5tream
       Character                              ReadStream
       Date                                   WriteStream
       Time                                       ReadWriteStream
       Number                                     ExternalStrea,
          Float                                        FileStream
          Fraction                         Random
          Integer                       Boolean
             LargeNegativelnteger          False
             LargePositivelnteger          True
             SmallInteger               ProcessorScheduler
       LookupKey                        Delay
          Association                   SharedQueue
   Collection                           Behavior
      SequenceableCollection               ClassDsscription
          LinkedList                          Class
             Semaphore                        MetaClass
          ArrayedCollection             BitBlt
             Array                         CharacterScanner
             WordArray                     Pen
                DisplayBitmap           DisplayObject
             RunArray                      DisplayMedium
             String                           Form
                Symbol                         Cursor
             Text                              DisplayScreen
             ByteArray                     InfiniteForm
          Interval                         OpaqueForm
          OrderedCollection                Path
             SortedCollection                  Arc
      Bag                                      Circle
      MappedCollection                         Curve
         Dictionary                            Line
            IdentityDictionary                 LinearFit
   Point                                       Spline
   Rectangle                            UndefinedObject

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

Классы и Метаклассы

Поскольку все компоненты системы Smalltalk-80 представляются объектами, а все объекты являются экземплярами классов, сами классы тоже должны представляться экземплярами некоторых классов. Класс, экземпляры которого сами являются классами называется метаклассом. Таким образом, каждый класс является единственным экземпляром своего собственного метакласса и, всякий раз, когда создается новый класс, для него автоматически создается новый метакласс. Как и другие классы, метакласс наследует информацию от суперкласса. Иерархия метаклассов образуется параллельно иерархии классов. Метаклассы похожи на другие классы, поскольку они содержат методы, используемые своими экземплярами.

Но метаклассы отличаются от других классов тем, что они не являются экземплярами своих метаклассов. Вместо этого, они все являются экземплярами класса с именем Metaclass (Метакласс). Кроме того, у метаклассов нет имен. Метакласс становится доступным, когда его экземпляру посылается сообщения class. Например, на метакласс класса Rectangle (Прямоугольник) можно сослаться выражением Rectangle class.

Когда метаклассы были добавлены в систему Smalltalk, был сделан следующий и последний шаг в организации классов. В системе определили абстрактный класс с именем ClassDescription (ОписаниеКласса) для описания классов и их экземпляров. Классы Class и Metaclass сделали подклассами класса СlassDescription. Как мы знаем, цепочка суперклассов любого класса заканчивается на классе Object, а сам класс Object не имеет суперкласса. А метакласс класса Object имеет своим суперклассом класс Class!

Сообщения метаклассам обычно посылаются для создания и инициализации экземпляров, а также для инициализации переменных класса.


Smalltalk: Синтаксис языка

"Чему же Вас учили?" - спросила Алиса.
"Сначала чихать и плясать, а затем четырем действиям арифметики: Служению, Почитанию, Угождению и Давлению." - ответил Мак-Тартль.

Льюис Кэррол.

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

  1. Литералы описывают некоторые постоянные объекты, такие как числа или строки символов.
  2. Имена переменных описывают доступные переменные. Значение имени переменной - текущее значение переменной с этим именем.
  3. Выражения посылки сообщения описывают сообщения, посланные получателю. Значение такого выражения определяется тем методом, которое вызывается сообщением. Поиск метода начинается с класса получателя.
  4. Выражения-блоки описывают объекты, представляющие отложенные действия. Блоки применяются для реализации управляющих структур.
Выражения находятся в двух местах: в методах и в тексте, изображенном на экране. Когда посылается сообщение, из класса получателя выбирается метод и его выражения выполняются. Интерфейс пользователя позволяет выбирать и выполнять выражения на экране. Подробности этого процесса выходят за пределы нашего обзора.

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

Литералы

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

Числа - объекты системы, которые представляют числовые значения и отвечают на сообщения, вычисляющие математические результаты. Литеральное представление десятичного числа стандартное. Например:

   3    30.45    -3    -14.0   13772
Числовые константы могут выражаться и не в десятичной системе счисления с помощью предшествующего цифрам префикса основания системы счисления. Префикс состоит из цифровой величины основания (всегда выраженной в десятичной системе) и следующей за ней буквы "r". Следующие примеры представляют числа в восьмеричной системе и шестнадцатеричной системе (в скобках даны их десятичная форма):
   8r377 (255)   8r34.1 (28.125)   8r-37 (-31)   16rFF (255)  16rAC.DC (172.859)
Числовые константы также могут выражаться в экспоненциальной форме с помощью экспоненциального суффикса. Экспоненциальный суффикс состоит из (латинской) буквы "е" и следующего за ним порядка, выраженного в десятичной системе счисления. Число, определенное перед экспоненциальным суффиксом, умножается на основание системы счисления, возведенное в степень, заданную порядком.
   1.586e5 (158600.0)  1.586e-3 (0.001586)   8r3e2 (192)

Символы

Символы - объекты, которые представляют собой отдельные элементы алфавита. Символьное литеральное выражение состоит из символа доллара ("$") и следующего за ним произвольного символа алфавита. Например:

  $t   $Ф   $-   $$   $1

Строки

Строки - это объекты, которые представляют собой последовательности символов. Литеральное представление строки - последовательность символов, заключенная в апострофы:

  'привет'  'food'  'Система Smalltalk-80'

Любой символ может быть включен в строку. Если апостроф сам входит в строку, то он должен дублироваться, чтобы избежать путаницы с ограничителем. Например, строковая литеральная константа

  'can''t'
представляет строку из пяти символов $c, $a, $n, $', $t.

Имена

Имена - объекты, которые представляют собой строки для именования элементов системы. Литеральное представление имени - последовательность символов, начинающаяся символом "#", например:
   #bill
   #М63
Есть некоторые ограничения на символы, которые могут быть в именах. Не существует разных имен с одинаковым набором символов; в системе каждое имя уникально.

Массивы

Массив - простая структура данных, на элементы которой можно ссылаться целыми индексами от 1 до числа, равного размеру массива. Литеральное представление массива - символ "#", за которым следует заключенная в круглые скобки последовательность других литералов - чисел, символов, строк, имен и массивов. Элементы массива отделяются пробелами. Перед вложенными в массив именами и массивами не ставится символ "#". Например, массив из семи строк описывается выражением
  #('food' 'utilities' 'rent' 'household' 'trasnport' 'taxes' 'recreation')
Массив из двух массивов и двух чисел описывается выражением
   #(('one' 1) ('not' 'negative') 0 -1)

Переменные и присваивание значений

Литеральные константы всегда ссылаются на один и тот же объект, а имена переменных в разное время могут ссылаться на различные объекты. Ссылка на объект по имени переменной изменяется тогда, когда выполняется выражение присваивания, то есть выражение, содержащее префикс присваивания. Префикс присваивания состоит из имени переменной, значение которой будет изменено, за которым следует левая стрелка "<-", которую в последних версиях языка Smalltalk можно заменять на более привычную пару символов ":=". Следующий пример - выражение с префиксом присваивания. Оно указывает, что переменная с именем quantity будет теперь ссылаться на объект, представляющий число 19:
     quantity <- 19

Сообщения

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

В сообщении previousTotal + increment, имя сообщения - символ "+". Сообщение просит получателя вычислить сумму. В дополнение к имени, сообщение содержит еще один объект - increment, определяющий прибавляемое значение. Дополнительные объекты в сообщении - его аргументы.

По своей структуре сообщения делятся на три группы: унарные, ключевые, бинарные.

Унарные сообщения - сообщения без аргументов, которое вовлекают в операцию только один объект - получатель сообщения. Имя унарного сообщения может быть любым простым идентификатором. Например,

   theta sin
   quantity sqrt
   namestring size
Ключевые сообщения - это общий тип сообщения с одним или более аргументами. Имя ключевого сообщения составляется из одного или более ключевых слов, по одному перед каждым аргументом. Ключевое слово - идентификатор с последующим двоеточием. Примеры ключевых сообщений:
   index max: limit
   ages at: 'Brett Jorgensen' put: 3
Когда на имя многоаргументного ключевого сообщения ссылаются независимо от получателя этого сообщения, ключевые слова сливаются воедино. Так, например, имя последнего сообщения - at:put:. Число ключевых слов в сообщении может быть любым, но большинство сообщений в системе имеют их не более трех.

Бинарные сообщения - специальный тип сообщения с одним аргументом. Имя бинарного сообщения состоит из одного или двух не алфавитно-цифровых символов. Единственное ограничение здесь то, что второй символ не может быть знаком минус. Бинарные сообщения обычно используются для арифметических и логических операций. Например,

  total - 1
  total <= max
Сообщения в языке Smalltalk обеспечивают двустороннюю связь. Имя и аргументы сообщения передают получателю информацию о том, какой тип ответа нужно подготовить. Получатель передает информацию обратно, возвращая отправителю объект, который становится значением выражения посылки сообщения. Если выражение включает префикс присваивания, то объект, возвращенный получателем, станет новым объектом, на который будет ссылаться переменная. Например, выражение
   sum <- 3 + 4
устанавливает, что число 7 будет новым значением переменной с именем sum.

Когда в одном выражении встречается несколько сообщений, они выполняются слева направо, при этом имеют место следующие правила синтаксического разбора:

  1. Унарные выражения разбираются слева направо.
  2. Бинарные выражения разбираются слева направо.
  3. Бинарные выражения имеют приоритет над ключевыми выражениями.
  4. Унарные выражения имеют приоритет над бинарными выражениями.
  5. Выражения, взятые в скобки, имеют приоритет над унарными выражениями.
Существует особая синтаксическая форма, называемая каскадированием, которая описывает посылку нескольких сообщений одному объекту. Любая последовательность сообщений может быть выражена и без использования этой формы. Однако каскадирование часто уменьшает необходимость в дополнительных переменных. Каскадное сообщение состоит из объекта-получателя, за которым располагаются несколько сообщений, разделенных точкой с запятой. Например:
   OrderedCollection new add: 1; add: 2; add: 3
Здесь три сообщения add: посылаются результату выражения OrderedCollection new. Без каскадирования для получения того же самого результата потребовалось бы четыре выражения (выражения друг от друга отделяются точкой) и переменная:
   |temp|
   temp <- OrderedCollection new.
         temp add: 1.
         temp add: 2.
         temp add: 3

Блоки

Блоки - объекты, используемые во многих управляющих структурах системы Smalltalk. Блок представляет собой отложенную последовательность действий. Выражение-блок состоит из заключенной в квадратные скобки последовательности выражений, разделяемых точками, точка стоящая после последнего выражения блока опускается. Например,
 [index <- index + 1] или [index <- index + 1. array at: index put: 0]
Когда блок встречается в выражении, заключенные в квадратные скобки выражения блока сразу не выполняются. Последовательность действий, которую описывает блок, будет выполнена только тогда, когда блок получит унарноe сообщение value (значение). Например, следующие два выражения дадут один и тот же результат:
         index <- index + 1  и  [index <- index + 1] value
Объект, возвращаемый после выполнения посланного блоку сообщения value, представляет значение последнего выполненного выражения блока. Блок, который не содержит никаких выражений ([]), когда ему посылается сообщение value, возвращает объект nil, неопределенный объект.

Управляющие структуры

Управляющие структуры определяют порядок некоторых действий. Фундаментальная управляющая структура в языке Smalltalk-80 обеспечивает последовательное выполнение следующих друг за другом выражений языка. Многие управляющие структуры, нарушающие последовательное выполнение выражений, могут быть реализованы с помощью блоков. Эти управляющие структуры вызываются либо посылкой сообщения блоку, либо посылкой сообщения с одним или более аргументами-блоками. Ответ на одно из эти сообщений и определяет порядок дальнейших действий посредством посылки блоку (или блокам) сообщения value.

Пример управляющей структуры, реализованной с помощью блоков, - простое повторение действий, представленное посылкой целому числу сообщения timesRepeat: с блоком в качестве аргумента. В ответ целое число посылает блоку-аргументу сообщение value столько раз, сколько единиц содержит значение числа. Следующее выражение четыре раза удвоит значение переменной с именем amount:

   4 timesRepeat: [amount <- amount + amount]

Условные выражения

Условные управляющие структуры используют два логических объекта true и false, логическую истину и логическую ложь. Логические объекты возвращаются после посылок сообщений, которые требуют простого ответа "да-нет" (например, сообщений для сравнения величин: <, <=, >, >=). Примером общей управляющей структуры, реализованной с помощью блоков, является условный выбор.

Условный выбор похож на оператор if-then-else в алголоподобных языках и обеспечивается сообщением к логическим объектам с именем сообщения ifTrue:ifFalse: (еслиИстина:еслиЛожь:) и двумя аргументами-блоками. Например, следующее выражение присваивает 0 или 1 переменной parity, в зависимости от того, делится на 2 значение переменной number или нет. Бинарное сообщение \\ вычисляет остаток от деления целых чисел.

   (number \\ 2) = 0
       ifTrue: [parity <- 0]
       ifFalse: [parity <- 1]
Предыдущий пример можно записать и по другому:
      parity <- (number \\ 2) = 0 ifTrue: [0] ifFalse: [1]
Для того, чтобы некоторые непоследовательные управляющие структуры, было легче реализовать, блоки могут иметь один или более аргументов. Аргументы определяются включением в начало блока идентификаторов, каждому из которых прошествует двоеточие. Аргументы блока отделяются от выражений, составляющих блок, вертикальной чертой. Следующий пример описывает блок с одним аргументом:
   [:array | total <- total + array size]
Типичное использование блоков с аргументами - реализация функций, применяемых ко всем элементам некоторой структуры данных. Например, многие объекты, представляющие собой различные виды структур данных отвечают на сообщение do: (выполнить:), которое имеет аргументом блок с одной переменной. Объект-получатель, получив сообщение do:, выполняет блок-аргумент один раз для каждого своего элемента. Каждый элемент из получателя становится значением аргумента блока в течении одного выполнения блока. Следующий пример вычисляет сумму квадратов первых пяти простых чисел. Результатом будет значением переменной sum.
   |sum|
   sum <- 0.
   #(2 3 5 7 11) do: [:prime | sum <- sum + (prime * prime)]
Объекты, которые реализуют подобные управляющие структуры, поставляют значения аргументам блока, посылая блоку сообщение value: anObject, с аргументом anObject, значение которого присваивается параметру блока. Например, выполнение следующих выражений приведет к тому, что переменная total будет иметь значение 7:
   |sizeAdder total|
   sizeAdder <- [:array | total <- total + array size].
   total <- 0.
   sizeAdder value: #(a b c).
   sizeAdder value: #(1 2).
   sizeAdder value: #(e f)
Блоки могут иметь и более одного аргумента. Все аргументы блока локальны по отношению к блоку и его выполнению. Например,
             [:x :y | (x * x) + (y * y)]
Приведенный блок может быть выполнен посредством посылки ему сообщения с именем value:value:. Два аргумента этого сообщения по порядку задают значения двум аргументам блока-получателя. Если блок получает сообщение с количеством ключевых слов value: отличным от числами аргументов блока, то будет выдано сообщение об ошибке.

О том, что еще интересного есть в Голубой Книге

"Есть три рода невежества: не знать ничего, знать дурно
то, что знаешь, и знать не то, что следовало бы знать."
Пьер Эмиль Дюкло

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

В системе Smalltalk-80 восемь важнейших категорий классов: ядро системы и его поддержка, линейные величины, числа, наборы, потоки, классы, независимые процессы и графика (см. приведенную выше часть иерархии классов). Детальные описания протокола каждого системного класса рассматриваются в двенадцати главах второй части книги. Здесь содержится энциклопедическая информация о протоколе класса: определяются категории сообщений, каждое сообщение комментируется, приводятся примеры. В представляемом протоколе класса описываются только те сообщения, которые вводятся самим классом. Полный протокол сообщений определяется просмотром протокола класса и всех его суперклассов. Поэтому полезно начать изучение с класса Object и продолжать его, спускаясь по иерархии классов вниз, с тем, чтобы наследуемый протокол изучался совместно с собственным протоколом класса. Еще в трех главах приводятся примеры использования базовых классов системы для построения небольших приложений.

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

Моделирование здесь - это представление множества объектов из реального или воображаемого мира в виде объектов некоторых классов. А цель создания компьютерной модели - предоставление рабочей средй для изучения моделируемой ситуации. Прежде чем создавать модели, в книге описывается иерархия классов, представляющих распределения вероятностей. Для определения, например, времени прибытия в модель объектов (клиентов в банк!) применяются различные виды распределения вероятностей. Они же используются и при случайном выборе времени ответа или времени обслуживания клиентов в модели. Класс SimulationObject (ОбъектМодели) представляет любой вид объекта, который вводится в модель для того, чтобы выполнить в ней одну или более задач; класс Simulation (Модель) представляет саму модель и обеспечивает управляющие структуры для ввода и формулировки задач каждому новому объекту модели.

Объекты, которые участвуют в управляемой событиями модели, действуют более или менее независимо друг от друга, используя имеющиеся в модели ресурсы. Поэтому необходимо решать проблему координации и синхронизации их действий. В системе Smalltalk-80 существуют средства синхронизации независимых событий, которые обеспечиваются классами Process (Процесс), Semaphore (Семафор) и SharedQueue (РазделяемаяОчередь). Классы, поддерживающие создание моделей, описывают использование объектами модели расходуемых, не расходуемых и/или возобновляемых ресурсов и предоставляют несколько способов сбора статистической информации о действующих моделях.

Для особенно любопытных, в первой главе третьей части описываются особенности системы с точки зрения разработчика, и объясняется как работает виртуальная машина Smalltalk.


Простой пример нового класса

"Не довольствуйся поверхностным взглядом!"
Марк Аврелий

Чтобы продемонстрировать то, о чем рассказывалось выше и отметить еще некоторые особенности, приведем простой пример, взятый из той же книги и слегка измененный нами далеко не лучшим образом только для того, чтобы в одном примере охватить побольше деталей (все равно не все!). Сначала, ничего не объясняя, приведем полное определение нового класса.

   class name                         FinancialHistory
   superclass                         Object
   instance variable names            allIncomes
                                      incomes
                                      allExpenditures
                                      expenditures
   class variable names               RateTax

   class methods
   initialize

      initialize
        RateTax <- 0.12

   instance creation

      initialBalance: amount
         ^super new setInitialBalance: amount

      new
         ^super new setInitialBalance: 0

   instance methods

   transaction recording

      receive: amount from: source
         incomes at: source
                put: (self totalReceivedFrom: source) + amount.
         allIncomes <- allIncomes + amount

      spend: amount for: reason
         expenditures at: reason
                     put: (self totalReceivedFrom: source) + amount.
         allExpenditures <- allExpenditures - amount

   inquiries

      cashOnHand
         ^allIncomes - allExpenditures

      amountOfTax
         ^(RateTax * allIncomes / (1 - RateTax)) truncate

      totalReceivedFrom: source
         (incomes includesKey: source)
            ifTrue: [^incomes at: source]
            ifFalse: [^0]

      totalSpentFor: reason
         (expenditures includesKey: reason)
            ifTrue: [^expenditures at: reason]
            ifFalse: [^0]

   private

      setInitialBalance: amount
            allIncomes <- amount.
            allExpenditures <- 0.
            incomes <- Dictionary new.
            expenditures <- Dictionary new
Описание класса начинается с заголовка, в котором указывается, что класс с именем FinancialHistory (ФинансовыйОтчет) создается как подкласс класса Object, он имеет одну переменную класса с именем RateTax (СтавкаНалога), доступную для всех его экземпляров, а каждый экземпляр класса имеет четыре переменных экземпляра, определяющих его структуру, с именами allIncomes (всеДоходы), incomes (доходы), allExpenditures (всеРасходы), expenditures (расходы). Первая и третья переменные будут хранить общие суммы всех доходов и расходов, соответственно, вторая и четвертая - словари, содержащие суммы и источники доходов и расходов.

Класс включает в себя часть, называемую class methods (методы класса), в которой описываются методы, добавляемые метаклассом для обращения к классу, как объекту системы. Таким образом, методы класса и методы экземпляра (instance methods) описываются вместе как части полного описания реализации класса.

В каждом классе методы разбиваются на категории, которые объединяют методы, используемые в родственных целях. Методы класса представлены в двух категориях. В первой, initialize, только один метод для инициализации переменной класса, во второй, instance creation, два метода для создания новых экземпляров класса, с разным количеством наличных денег.

Обратите внимание, что методы создания экземпляров (сообщения initialBalance: и new) не имеют прямого доступа к переменным нового экземпляра, поскольку являются не частью класса, которому принадлежат новые экземпляры, а частью класса класса (метакласса). Поэтому методы создания экземпляров сначала создают экземпляры с неинициализированными переменными, а потом посылают им сообщение инициализации setInitialBalance:. Метод для этого сообщения находится в описании реализации класса FinancialHistory уже среди методов экземпляра. Этот метод "умеет" присваивать переменным экземпляра соответствующие значения. Сообщение инициализации не считается частью внешнего протокола класса FinancialHistory, а классифицируется как частное и находятся в категории частных методов (private). Обычно такие сообщения посылаются только другими методами.

Методы экземпляра представлены еще в двух категориях. В категории transaction recording, содержаться методы которое позволяют запомнить откуда и сколько получено доходов (receive: amount from: source), и сколько и по какой причине потрачено (spend: amount for: reason). В категории inquiries, содержаться методы которое позволяют определить сколько получено денег из данного источника (totalReceivedFrom: source), сколько денег потрачено по указанной причине (totalSpentFor: reason), сколько еще осталось денег (cashOnHand) и, наконец, сколько из всего заработанного было уплачено подоходного налога (amountOfTax), предполагая ради простоты, что ставка налога не зависит от суммы и равна 12%.

Используемые в телах методов псевдопеременные self и super всегда ссылается на объект, вызвавший для исполнения этот метод. Между ними есть существенное различие: поиск метода, посланного self всегда начинается с класса, которому принадлежит приемник сообщения, а поиск метода, посланного super начинается с суперкласса того класса, в которм найден метод, содержащий псевдопеременную super. Символ ^ - стрелка вверх, это символ возврата значения, который, если встречается по ходу вычисления, допускает вычисление только выражения стоящего непосредственно после него (до следующей за ним точки!), прекращает выполнение метода и возвращает результат этого выражения, как результат всего метода.

Чтобы использовать класс, надо сначала его инициализировать:

     FinancialHistory initialize.
После этого можно создать книгу учета доходов и расходов некой семьи, посредством вычисления следующих выражений:
FamilyFinancialHistory <- FinancialHistory new.
FamilyFinancialHistory receive: 500000 from: 'зарплатаЖены';
                       receive: 400000 from: 'зарплатаМужа';
                       receive: 300000 from: 'пенсияТещи'.
FamilyFinancialHistory spend: 150000 for: 'наПродукты';
                       spend:  50000 for: 'наКниги';
                       spend: 300000 for: 'наОдежду'.
Если теперь вычислим выражение FamilyFinancialHistory cashOnHand, то с удивлением обнаружим, что еще осталось 700,000 руб до следующей получки!

Завершая статью, вспомним фразу еще одного древнего не программиста: "Вполне вероятно наступление невероятного!"(Агафон) и отнесем ее к вероятности выхода Голубой Книги программирования на Русском языке!

Кирютенко Юрий Александрович
Савельев Василий Александрович
Ростовский государственный университет.
26 июня 1997 г.

Smalltalk в России РГУ Smalltalk в РГУ LinksСсылки

Copyright © Ю.А. КирютенкоВ.А. Савельев, 1997

Россия

344090  Ростов-на-Дону
ул. Зорге, д.5
ауд. 153, 225
[Counter]