Smalltalk часто воспринимают как язык из музея: был до Java, остался в учебниках. На самом деле Pharo — современная открытая реализация Smalltalk. Её особенность в том, что программа живёт не в файлах, а в образе: вы меняете классы и методы прямо в запущенной среде, сразу проверяете гипотезу и сохраняете результат.
Почему Smalltalk удивляет
В Smalltalk нет разделения на код и среду выполнения. Есть образ — файл со всеми объектами, классами и методами, — и виртуальная машина, которая его исполняет. Вы пишете выражение в Playground и сразу получаете результат. Если выражение падает, открывается отладчик с полным стеком: метод можно править и продолжать, не перезапуская программу.
Отсюда мгновенная обратная связь. Отсюда же сложность: исходники — не главный артефакт, а образ нужно версионировать иначе, чем папку с файлами.
Установка Pharo
Самый быстрый способ на Linux и macOS — официальный скрипт zeroconf. Он скачивает виртуальную машину, образ и файл изменений в текущую папку.
mkdir ~/pharo && cd ~/pharo
curl https://get.pharo.org/64/ | bash
./pharo Pharo.image eval "3 factorial"
./pharo Pharo.image
Третья команда проверяет запуск без графики и печатает 6, четвёртая открывает среду с окнами. Для повседневной работы удобнее Pharo Launcher: он хранит несколько образов и создаёт чистые копии. На Windows это практически безальтернативный вариант.
Всё есть объект
Синтаксис Smalltalk короткий, потому что управляющих конструкций нет: if, while и циклы — обычные сообщения объектам. Сообщения бывают унарные (factorial), бинарные (+, *) и ключевые (at:put:). Порядок вычисления — слева направо, скобки его меняют.
3 factorial. "6"
'hello' size. "5"
#(1 2 3) inject: 0 into: [ :sum :each | sum + each ].
(1 to: 5) select: [ :n | n odd ]. "OrderedCollection(1 3 5)"
Transcript showln: 'готово'.
Результат выражения Playground показывает справа. В двойных кавычках — комментарий, $a — отдельный символ, #(1 2 3) — литерал массива. Точка разделяет выражения, точка с запятой означает каскад.
Блоки и коллекции
Блок — замыкание в квадратных скобках. Оно не выполняется сразу, а передаётся как объект: его можно сохранить, вызвать через value, вернуть из метода. На блоках построены все итераторы коллекций.
| numbers doubled |
numbers := #(1 2 3 4 5).
doubled := numbers collect: [ :each | each * 2 ].
numbers do: [ :each | Transcript showln: each ].
(numbers select: [ :each | each even ]) size. "2"
Вертикальные черты объявляют временные переменные. Коллекции богатые, и почти любая задача решается цепочкой select:, collect:, detect:, inject:into: без циклов со счётчиком.
Свой класс за минуту
Класс создаётся отправкой сообщения subclass: существующему классу. Методы добавляют из Playground или браузера классов.
Object subclass: #Counter
instanceVariableNames: 'value'
classVariableNames: ''
package: 'Shop'.
Counter compile: 'increment
value := (value ifNil: [ 0 ]) + 1'.
Counter compile: 'value
^ value ifNil: [ 0 ]'.
Counter new increment; increment; value. "2"
Селектор, тело, возврат через ^ — вот и весь метод. ifNil: — сообщение, а не конструкция языка, поэтому else-ветка передаётся блоком. Каскад increment; increment; value вызывает три метода у одного объекта и возвращает результат последнего.
Инструменты среды
Pharo — это ещё и набор инструментов из меню World:
- Playground — блокнот для выражений, главное окно экспериментов.
- System Browser — навигация по пакетам, классам и методам, правка на ходу.
- Inspector — состояние любого объекта по полям, с изменением значений.
- Debugger — открывается при ошибке, позволяет править метод и повторять выражение.
- Finder — поиск классов и методов по содержимому.
- TestRunner — запуск тестов и просмотр результатов.
Отладчик попробуйте первым делом: поставьте self halt, вызовите метод и посмотрите на стек. Это главный аргумент в пользу Smalltalk для обучения.
Пакеты и зависимости
Сторонний код ставится через Metacello — менеджер пакетов, который читает baseline-описание из Git-репозитория.
Metacello new
baseline: 'MyApp';
repository: 'github://myorg/myapp:main';
load: 'core'.
Для HTTP есть Zinc, для JSON — NeoJSON, для сериализации — STON. Этого хватает, чтобы написать небольшой сервис на Seaside или голом Zinc.
Тесты
Тесты пишутся как классы-наследники TestCase и запускаются из TestRunner или Playground.
TestCase subclass: #CounterTest
instanceVariableNames: ''
classVariableNames: ''
package: 'Shop'.
CounterTest compile: 'testIncrement
| counter |
counter := Counter new.
counter increment; increment.
self assert: counter value equals: 2'.
CounterTest suite run.
setUp выполняется перед каждым тестом, tearDown — после. Проверок немного: assert:, assert:equals:, deny:, should:raise:. Этого достаточно: тестируют поведение объектов, а не глобальное состояние.
Сохранение и запуск без графики
Образ можно сохранить вместе с кодом и запускать на сервере как обычный процесс. Команда save создаёт копию, флаг —headless отключает окна.
./pharo Pharo.image save shop
./pharo --headless shop.image eval "Smalltalk version"
./pharo --headless shop.image eval "ShopServer start"
Для продакшена образ собирают в CI, кладут рядом с VM и запускают через systemd. Образ содержит и код, и данные, поэтому его нельзя перезаписать на ходу: процесс останавливают, затем заменяют файлы. Часто код грузят при старте из Git через Metacello, а образ держат минимальным.
Диагностика и типичные проблемы
- Smalltalk version и Smalltalk vm version — версия образа и виртуальной машины.
- Smalltalk image imagePath — откуда запущен образ, полезно при путанице с копиями.
- Из cron нет доступа к дисплею, поэтому используйте —headless.
- Ошибка «image is read-only» — образ в папке без прав на запись, скопируйте его.
Если среда не стартует, проверьте файл .changes рядом с образом и совпадение версий VM и образа. При переносе копируйте оба файла, а не только image.
Кому это нужно
Pharo выбирают для обучения ООП, прототипов, исследовательских проектов и нишевых веб-приложений. Он не заменит Python или Go: вакансий мало, экосистема небольшая. Понимание сообщений, блоков и живой среды меняет взгляд на любой язык. Для продакшена из Pharo получается компактный сервис без внешних зависимостей.
Итог
Smalltalk в Pharo — рабочая среда с мгновенным циклом правок и мощным отладчиком. Начните с zeroconf-установки, выполните выражения в Playground, создайте класс Counter и запустите тест. Затем переходите к Metacello и Iceberg и попробуйте собрать HTTP-сервис на Zinc. Опыт системы, где всё является объектом, пригодится и в другом стеке.