Pharo

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 20:05, 1 июня 2016.
Pharo
Pharo.png
Парадигма объектно-ориентированный
Спроектировано Pharo Board
Первый   появившийся 2008
Стабильная версия 4.0 / 16 апреля 2015
Печать дисциплины Динамическая, Унарная
Лицензия MIT, частично Apache 2.0
Портал: http://pharo.org/
Под влиянием
Smalltalk, Squeak

Pharo ‒ это открытая реализация языка программирования Smalltalk и окружающей среды. Pharo предлагает сильные живые черты программирования, такие как непосредственные манипуляции с объектом, обновление в режиме реального времени, "горячая перекомпиляция". "Живая" среда программирования находится в самом сердце системы. Pharo это греческое слово (Φάρος), что означает маяк. Логотип Pharo ‒ рисунок маяка внутри последней буквы O имени.

Pharo Дзен

Значение Pharo и убеждения разработчиков перечислены в следующем простом списке.

  • Легко понять, легко изучить, легко изменить.
  • Иерархия, направленная вниз.
  • Примеры для обучения.
  • Полностью динамичный и податливый.
  • Красота в коде, красота в комментариях.
  • Простота является пределом элегантности.
  • Лучше набор небольших полиморфных классов, чем один большой и ужасный.
  • Классы структурируют нашу лексику.
  • Сообщения ‒ наша лексика.
  • Полиморфизм наш эсперанто.
  • Абстракция и композиция наши друзья.
  • Тесты важны, но они могут быть изменены.
  • Явное лучше, чем неявное.
  • Магия только в нужном месте.
  • Шаг за шагом.
  • Нет ничего важного для исправления.
  • Извлечение уроков из ошибок.
  • Совершенствование может убить движение.
  • Качество ‒ это появление свойств.
  • Простые решения, чтобы поддержать прогресс.
  • Связь является ключевым.
  • Система с надежной абстракцией ‒ то, что способен понять один человек.

Синтаксис в двух словах

Pharo, как и большинство современных диалектов Smalltalk, имеет синтаксис очень близкий к тому, что и Smalltalk-80. Он устроен так, что текст программы можно читать вслух, как будто это своего рода упрощённый английский:

(Smalltalk includes: Class) ifTrue: [ Transcript show: Class superclass ]

Синтаксис у Pharo минималистичный. По существу он состоит только из отправки сообщений (например, выражений). Выражения строятся из очень небольшого числа примитивных элементов. Есть только 6 ключевых слов, и нет синтаксических структур управления и операторов создания новых классов. Вместо этого, почти всё достигается путем отправки сообщений объектам. Например, вместо if-then-else структуры управления, Smalltalk отправляет сообщения ifTrue: логическому объекту. Новые (суб) классы создаются путем отправки сообщения суперклассу.

Синтаксические элементы

Выражения строятся из следующих элементов:

  1. Шесть зарезервированных ключевых слов, или псевдопеременных: self, super, nil, true, false, и thisContext;
  2. Константные выражения для литеральных объектов, включающие цифры, символы, строки, массивы;
  3. Определение переменных;
  4. Задание значений переменным;
  5. Блоки сообщений;
  6. Сообщения.

Далее в таблице идут примеры различных синтаксических элементов:

Синтаксис Что обозначает
startPoint имя переменной
Transcript имя глобальной переменной
self псевдопеременная
1 десятичное целое число
2r101 двоичное целое число
1.5 вещественное число
2.4e7 экспоненциальная запись числа
$a символ 'a'
’Hello’ строка 'Hello'
#Hello символ #Hello
#(1 2 3) массив из символов
{1. 2. 1+2} динамический массив
"a comment" комментарий
x := 1 присваивание переменной x значения 1
[ x + y ] блок, вычисляющий значение x+y
<primitive: 1> виртуальный примитивный метод
3 factorial унарное сообщение
3+4 бинарное сообщение
2 raisedTo: 6 modulo: 10 ключевое сообщение
↑true возврат значения true
Transcript show: ’hello’. Transcript cr разделитель предложений (.)
Transcript show: ’hello’; cr разделитель каскадных сообщений (;)

Локальная переменная startPoint ‒ это имя переменной, или идентификатор. По соглашению, идентификаторы состоят из слов “camelCase” стиля (то есть, каждое слово, за исключением первого, пишется с заглавной буквы). Первая буква переменной экземпляра, метода, блока аргумента, или временной переменной должны быть в нижнем регистре. Это указывает, что переменная имеет ограниченную область видимости.

Общие переменные. Их идентификаторы начинаются с прописных букв, и к ним относятся глобальные переменные, переменные класса, общие словари или имена классов. Transcript ‒ глобальная переменная, являющаяся экземпляром класса TranscriptStream.

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

Числа. В дополнение к обычным десятичных числам, таким как 42, Pharo также обеспечивает поддержку других систем счисления. 2r101 представляет число 101 в системе счисления 2 (т.е. двоичной), которое равно десятичному 5.

Вещественные числа могут быть записаны с использованием их базового десятичного экспоненциального представления: 2.4e7 2,4 × 107.

Литералы. Знак доллара представляет литерал: $а буквально обозначает 'а'. Экземпляры непечатных символов может быть получены путем отправки сообщения с соответствующим названием к классу Character, таких как Character space и Character tab.

Строки. Одинарные кавычки используются для определения текстовой строки. Если вы хотите использовать строку с цитатой внутри, просто продублируйте одинарную кавычку.

Символы, как и строки содержат последовательность букв. Однако, в отличие от строк, символ гарантированно будет глобально уникальным. Например, существует только один объект символ #Hello, но может быть несколько объектов строк со значением 'Hello'.

Массивы, создаваемые во время компиляции определяются при помощи #(), где между скобками можно вводить объекты литералы, разделяя их пробелами. Всё в скобках должно быть постоянным на время компиляции. Например, #(27 (true false) abc) является массивом, состоящим из трех элементов: целого числа 27, массива, создаваемого во время компиляции, содержащего два булевых элемента, и символа #abc. (Заметим, что тоже самое можно записать в виде #(27 #(true false) #abc).)

Массивы создаваемые во время выполнения метода. Фигурные скобки {} определяют (динамические) массивы, создаваемые во время выполнения метода. Элементы выражений, разделены точками. Так {1. 2. 1 + 2} определяется массив с элементами 1, 2, и результатом вычисления 1 + 2. (Запись в фигурных скобках свойственна Pharo и Squeak диалектам Smalltalk! В других версиях Smalltalks необходимо объявлять динамические массивы в явном виде.)

Комментарии заключаются в двойные кавычки. Так "hello" является комментарием, а не строкой, и игнорируется компилятором Pharo. Комментарии могут занимать несколько строк.

Вертикальные полосы определяют локальные переменные. | | заключают в себе объявление одной или нескольких локальных переменных в методе (а также в блоке). Выражение : = присваивает объект к переменной.

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

Примитивы. <primitive: ...> обозначает ссылку на примитив виртуальной машины. (<primitive: 1> является виртуальной машиной примитивов для сложения SmallInteger) Любой код, следующий за примитивом выполняется, только если примитив выполнен с ошибкой. То же синтаксис используется для метода аннотаций.

Унарные сообщения состоящие из одного слова (например factorial) присылаются получателю (например, 3).

Бинарные сообщения ‒ это операторы (такие как +), отправленные получателю и принимающие один аргумент. Например 3+4. Тут в роли приемника выступает 3, а аргументом является 4.

Ключевые сообщения состоят из нескольких ключевых слов (как raisedTo:modulo:), каждое из которых заканчивается двоеточием и принимает один аргумент. В выражении 2 raisedTo: 6 modulo: 10, селектор сообщения raisedTo:modulo: принимает два аргумента: 6 и 10, каждый из которых расположен после двоеточия. Сообщение отправляется получателю 2.

Возврат значения. Используется для возврата значения из метода. (Вы должны ввести ^ на клавиатуре, чтобы получить символ ↑).

Последовательности выражений. Точка или (.) является разделителем выражений. Ввод точки между двумя выражениями превращает их в независимые утверждения.

Каскадные сообщения. Точка с запятой может использоваться для отправки сообщений каскадом одному получателю. В Transcript show: 'hello'; cr сначала отправляется сообщение show: 'hello' получателю Transcript, а затем посылается унарное сообщение cr тому же получателю.

Псевдопеременные

В Smalltalk, есть 6 зарезервированные ключевых слова, или псевдопеременных:

  1. nil,
  2. true,
  3. false,
  4. self,
  5. super,
  6. thisContext.Их называют псевдопеременными, потому что они предопределены и не могут быть изменены. true, false и nil являются константами, а значение self, super и thisContext зависит от места расположения в коде.
  • true и false являются уникальными экземплярами булевых классов True и False.
  • self всегда ссылается на получателя сообщения, в котором расположен выполняемый в данный момент метод.
  • super также ссылается на получателя, в котором расположен текущий метод, но, когда вы посылаете сообщение для super, method-lookup изменяется так, что он начинает с суперкласса того класса, который содержит метод, используемый super.
  • nil является неопределенным объектом. Это единственный экземпляр класса UndefinedObject. Переменные экземпляров, переменные класса и локальные переменные изначально инициализируются значением nil.
  • thisContext ‒ это псевдопеременная, которая представляет верхнюю рамку стека выполнения программы. Другими словами, она представляет собой в настоящее время исполняющийся MethodContext или BlockClosure. thisContext обычно не представляют интереса для большинства программистов, но она крайне важна для реализации инструментов развития, таких как отладчик, и также используется для реализации обработки исключений и продолжений.

Отправка сообщений

В Pharo есть три вида сообщений:

  1. Унарные сообщения не принимающие аргументов. Например, 1 factorial посылает сообщение factorial объекту 1.
  2. Бинарные сообщения имеют ровно один аргумент. 1 + 2 посылает сообщение + с аргументом 2 объекту 1.
  3. Ключевые сообщения принимают произвольное число аргументов. 2 raisedTo: 6 modulo: 10 отправляет сообщение, состоящее из селектора raisedTo:modulo: и аргументов 6 и 10 объекту 2.

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

+ − / \ * ~ < > = @ % | &amp; ? ,

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

Унарные сообщения имеют наивысший приоритет, затем идут двоичные сообщения и, наконец, потом располагаются ключевые сообщения. Так:

2 raisedTo: 1 + 3 factorial → 128

(Сначала мы отправляем factorial объекту 3, далее посылаем +6 к 1, и, наконец, мы направляем raisedTo:7 к 2.) Далее мы будем использовать запись вида: выражение → результат для показа результата вычисления выражения.

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

1 + 2 * 3 → 9

Для установки приоритета необходимо использовать скобки:

1 + (2 * 3) → 7

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

Transcript cr.
Transcript show: 'hello world'.
Transcript cr

Сначала объекту Transcript будет отправлено сообщение cr, затем show: 'hello world', и, наконец, отправлено ещё раз cr.

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

Transcript cr;
show: 'hello world';
cr

Эта запись приводит к той же последовательности операций, что и предыдущий пример.

Пример

Ниже приведён пример простой игры Lights Out, написанной на Pharo.

"[1] Определение класса #LOCell"
SimpleSwitchMorph subclass: #LOCell
	instanceVariableNames: 'mouseAction'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'PBE-LightsOut'

"[2] Инициализация переменных экземпляра LOCell"
initialize
	super initialize.
	self label: ''.
	self borderWidth: 2.
	bounds := 0@0 corner: 16@16.
	offColor := Color paleYellow.
	onColor := Color paleBlue darker.
	self useSquareCorners.
	self turnOff

"[3] Типичный метод, устанавливающий значение"
mouseAction: aBlock
	↑mouseAction := aBlock

mouseMove: anEvent
	"Do nothing"

"[4] Обработчик события"
mouseUp: anEvent
	mouseAction value

"[5] Определение класса LOGame"
BorderedMorph subclass: #LOGame
	instanceVariableNames: 'cells'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'PBE-LightsOut'!

"[6] Метод-константа"
cellsPerSide
	"The number of cells along each side of the game"
	↑10

"[7] Инициализируем игру"
initialize
	| sampleCell width height n|
	super initialize.
	n := self cellsPerSide.
	sampleCell := LOCell new.
	width := sampleCell width.
	height := sampleCell height.
	self bounds: (5@5 extent: ((width * n) @ (height * n)) + (2 * self borderWidth)).
	cells := Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ].

"[8] Вспомогательный метод для инициализации"
newCellAt: i at: j
	"Create a cell for position (i,j) and add it to my on-screen
	representation at the appropriate screen position. Answer the new cell"

	| cell origin |

	cell := LOCell new.
	origin := self innerBounds origin.
	self addMorph: cell.
	cell position: ((i - 1) * cell width) @ ((j - 1) * cell height) + origin.
	cell mouseAction: [self toggleNeighboursOfCellAt: i at: j].
	^ cell! !
        ↑c

"[9] Метод обратного вызова"
toggleNeighboursOfCellAt: i at: j
	(i > 1) 				    ifTrue: [ (cells at: i - 1 at: j) toggleState ].
	(i < self cellsPerSide)  ifTrue: [ (cells at: i + 1 at: j) toggleState ].
	(j > 1)                         ifTrue: [ (cells at: i at: j - 1) toggleState ].
	(j < self cellsPerSide) ifTrue: [ (cells at: i at: j + 1) toggleState ].
  1. Определение класса #LOCell. Это новое определение состоит из выражения Smalltalk, которое посылает сообщение к существующему классу SimpleSwitchMorph, с целью создания подкласса, называемого LOCell. (На самом деле, так как LOCell еще не существует, мы передали в качестве аргумента символ #LOCell который представляет имя создаваемого класса) Мы также указываем, что экземпляры нового класса должны иметь переменную экземпляра mouseAction, которую мы будем использовать для определения действий, которые должна совершить ячейка, при клике на неё мышью.
  2. Инициализация переменных экземпляра LOCell. Первое, что этот метод делает, это выполняет инициализацию его суперкласса, SimpleSwitchMorph. Идея здесь в том, что любое унаследованное состояние должно правильно инициализироваться методом инициализации суперкласса. Это хорошая идея, инициализировать унаследованное состояние путем отправки super initialize, прежде чем делать что-нибудь еще; мы не знаем точно, что метод инициализации SimpleSwitchMorph будет делать, но мы уверены, что он установит некоторым переменным экземпляра разумные значения по умолчанию, так что нам лучше вызвать его, иначе есть риск начать с несогласованного состояния. Остальная часть метода инициализирует состояние нового объекта. Отправка self label: ' ', например, устанавливает метку этого объекта в пустую строку. Выражение 0@0 corner: 16@16, вероятно, нуждается в объяснении. 0@0 представляет собой объект класса Point с координатами х и у, установленными в 0. В самом деле, 0@0 посылает сообщение @ числу 0 с аргументом 0. Результатом будет то, что число 0 "попросит" класс Point создать новый экземпляр с координатами (0,0). Теперь мы посылаем вновь созданной точке сообщение corner: 16@16, которое создаёт прямоугольник с углами 0@0 и 16@16. Этот прямоугольник будет присвоен переменной bounds, унаследованной от суперкласса. Обратите внимание, что начало координат в Pharo находится в левом верхнем углу экрана, и у растёт сверху вниз
  3. Типичный метод, устанавливающий значение. Метод устанавливает значение переменной ячейки mouseAction равным переданному аргументу, а затем возвращает новое значение. Любой метод, который изменяет значение переменной экземпляра в этом случае называется методом записи; метод, который отвечает на текущее значение переменной экземпляра называется методом получения.
  4. Обработчик события. Метод отправляет сообщение value объекту, хранящемуся в переменной экземпляра mouseAction.
  5. Определение класса LOGame. Здесь мы создаём подкласс, наследуемый от класса BorderedMorph; Morph является суперклассом всех графических форм в Pharo, а BorderedMorph ‒ это Morph рамкой.
  6. Метод-константа. Этот метод вряд ли можно сделать проще: он постоянно возвращает константу 10. Одним из преимуществ, представлений констант как методов является то, что если программа развивается так, что константа начинает зависеть от некоторых других свойств системы, то метод может быть изменён для вычисления нужного значения.
  7. Инициализируем игру. Строка | sampleCell width height n | объявляет 4 временных переменных. Smalltalk выражение self cellsPerSide посылает сообщение cellsPerSide объекту self, т.е. этому самому объекту. Результатом будет число клеток, приходящихся на одну сторону игрового поля, которое присваивается переменной n. Затем мы создаем новый объект LOCell. После этого мы устанавливает границы нового объекта. Последняя строка присваивает переменной cells класса LOGame вновь созданную матрицу с нужным количеством строк и столбцов. Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ] создаёт новую матрицу размером n×n и инициализирует её элементы. Начальное значение каждого элемента будет зависеть от его координат. (i,j)-й элемент будет инициализирован в результате вычисления self newCellAt: i at: j.
  8. Вспомогательный метод для инициализации. Метод возвращает новый экземпляр класса LOCell, специализированный для позиции (i, j) в матрице ячеек. Последняя строка определяет свойство mouseAction новой ячейки в виде блока [self toggleNeighboursOfCellAt: i at: j ]. В сущности, это определяет поведение для обратного вызова при клике мышью.
  9. Метод обратного вызова. Метод переключает состояние четырех ячеек с северной, южной, западной и восточной сторон клетки (i,j). Единственная сложность заключается в том, что доска конечна, так что мы должны убедиться, что соседняя ячейка существует, прежде чем переключать её состояние.

Литература

  1. Pharo
  2. Pharo vision document
  3. Pharo by example

Автор статьи: Желудков А.В.