Читаю прелюбопытнейшую книжку — «Семь языков за семь недель«. Языков программирования, разумеется. Читаю и пребываю в перманентном восторге. По списку идут Ruby, IO, Prolog, Scala, Closure, Erlang и Haskell, и я только-только добрался до Scala. Prolog, разумеется, здорово прошелся по психике в шипастых сапожищах, но больше всего в душу запал IO.
Оказывается, кроме JavaScript в мире существуют другие прототипные языки программирования, такие как IO, и JavaScript не обязательно хорошо смотрится на их фоне. Меня впечатлили три момента.
Момент 1: создание объектов.
В IO объекты не создаются, а клонируются.
1 |
obj := Object clone |
Поскольку это клон, его поле (слот) proto, разумеется, указывает на Object .
1 |
obj proto == Object //==> true |
Всё прозрачно и очевидно. Сразу ясно, кто объект-папа, и каким образом он получил потомство.
В моём любимом JavaScript узнать, кто папа, можно только после генетического теста или чтения мануалов.
1 2 |
var obj = {}; console.log("Who's your daddy? ", obj.__proto__); //? |
Те редкие люди, которые подумали, что __proto__ указывает на Object либо null , бесконечно неправы, потому что он указывает на Object.prototype .
({}).__proto__ === Object.prototype //true
А Object — это вообще функция. Можно выпендриться и возразить, мол, функции в JavaScript это всего-лишь исполняемые объекты, но это читерство.
Создание объектов через функции-конструкторы — это вообще фантастический изврат.
1 2 3 4 5 6 7 8 9 10 11 |
var Point = function () { this.x = 0; this.y = 0; }; console.log(Point()); //undefined console.log(new Point()); //Point {x: 0, y: 0} var p = new Point(); console.log(p.__proto__ === Point); //false console.log(p.__proto__ === Point.prototype); //true |
Если забыть new перед функцией-конструктором, получим undefined . Если не забыть new , но вернуть из функции что-либо кроме примитивов — созданный объект потеряется в преисподней. Ну и да, прототип создаваемого объекта берется из Point.prototype . Всё можно понять и объяснить, но зачем???
Легион JavaScript программистов на протяжении многих лет пытался эмулировать классовое наследование, потому что синтаксис к этому всячески подталкивает. С синтаксисом IO такое бы даже в голову не пришло.
Момент 2: обмен сообщениями.
Вместо того, чтобы вызвать метод или обратиться к полю напрямую, мы отправляем объекту сообщение, а тот уже смотрит, есть ли такой слот. Если есть, возвращает значение или вызывает метод. Если нет — может и упасть.
Звучит просто, но последствий от такого дизайнерского решения уйма. Во-первых, оно совсем по-другому читается. Во-вторых, входящее сообщение можно всячески исследовать изнутри обработчика — проверить имя, аргументы, перенаправить другому объекту. Например, вот так метод может узнать, под каким именем его прилепили к объекту:
1 2 3 4 5 6 7 |
obj := Object clone obj getMethodNameFromInside := method( writeln(call message name) ) obj getMethodNameFromInside //"getMethodNameFromInside" |
Анархия начинается, если прикрепить к объекту обработчик неизвестных сообщений.
Момент 3: IO бесконечно расширяемый.
В IO можно добавлять свои и убирать существующие операторы, можно расширять синтаксис, можно вообще удалить clone метод у Object, и текущий рантайм будет безвозвратно испорчен.
Просто из чистого вандализма, я бы запретил числам делиться:
1 2 3 4 5 |
Number / := method(val, "Bazinga!" ) writeln(1 / 2) //==> Bazinga! |
Но параллельно добавил бы поддержку массивов-литералов [] , как в JavaScript:
1 2 3 4 5 |
squareBrackets := method( list() ) writeln([]) //list() |
Этот пример немного бесполезный, потому что пустая коллекция мало кому нужна. Чуть допилив напильником, можно получить полноценный массив-литерал:
1 2 3 4 5 6 7 |
squareBrackets := method( call message arguments map(element, doMessage(element) ) ) writeln([1, 2, 5/2, "he".."llo"]) //list(1, 2, 2.5, "hello") |
Всё, что внутри квадратных скобок, передается в метод squareBrackets, а doMessage исполняет аргументы как сообщения, чтобы положить в массив готовые результаты.
В IO есть свой эквивалент method_missing от Ruby. Называется он forward , и будет вызван, если объект получил незнакомое сообщение:
1 2 3 4 5 6 7 8 9 10 |
person := Object clone person name := "John" person forward := method( writeln("Did you just call me ", call message name, "?") ) writeln(person name) //==> John person jerk //==> Did you just call me jerk? |
Одним словом, рай для метапрограммирования.
Резюме.
В продакшен IO я бы однозначно не пустил. Время от времени интерпретатор вывалился с какой-то ошибкой на вполне безобидных примерах. Но я давно не получал такого удовольствия, делая программистские задачки. Много наблюдений, много мыслей, и очень интересно сравнивать IO с JavaScript в вещах, которые уже давно воспринимал как понятную данность. То же создание объектов, отношение к прототипам, и общее восприятие готовых программ. Мозг пострадал, но стал больше.
Лично я буду ждать отзыв и впечатления о Scala) ну и о Erlang до кучи.
Урррраааа — у меня взорвался мозг! 🙂 Клёва! 🙂