Pull to refresh
1
0
Dmitry Soshnikov @dsCode

User

Send message
Ну а все-таки (без «паразитов» ;)). В чем смысл?

P.S.: я достаточно глубоко знаю обе парадигмы.
> Да ладно?

Ага. Конкретика имеет меньшее значение, чем одинаковые методики разрешения имет свойств/методов и динамика в целом. Естественно, относительно конкретики, есть отличия. Но основа та же делегация.

Другой пример, как уже было отмечено — CoffeeScript. На своем уровне абстракции и в своей семантике он классовый (точнее, классово-прототипный). Но как и в Python, классы там всего лишь сахар над «делегацией».

P.S.: и Ruby такой же. И Lua.
А смысл? Если удобней просто this.super.
> Ошибка тут будет в том, чтобы считать Python классовым языком — скорее, он прототипный

Абсолютно верно, Python — прототипный язык с классам в виде «сахара». Такие же классы в CoffeeScript. Такие же планируются (и сейчас активно обсуждаются в es-discuss) в следующей версии JS.
> ВСЕГДА

Не всегда ;) gist.github.com/1330574 — можно вполне себе нормально использовать this.super, без хардкода имен классов.

Существуют и другие методики, e.g.: github.com/DmitrySoshnikov/def.js/blob/master/def.js#L80

Помимо этого, удобного this.super можно добиться через wrapper'ы.
Да, похожий сахар будет. И называется это rest и spread:

function foo(...arguments) {
  console.log(arguments);
}

foo(1, 2, 3); // [1, 2, 3]

И обратно (spread):

function foo(a, b, c) {
  console.log(a, b, c);
}

var args = [1, 2, 3];

foo(...args); // 1, 2, 3

Слайд 23 — здесь.
Основной протест был в поддержке не только «функций-вызовов», но и функций, как объектов. I.e.: не только так: foo.bar(), но и foo.bar.apply(...). Записи должны (?) быть семантически эквивалентны. Достичь этого можно лишь всегда возвращая функцию-активатор. Но я тоже ратовал за isCall флаг.
Хорошая статья, поздравляю (Хабр снова начинает становится «тортом»! ;))

Ниже пара дополнений.

P.S.:

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


Кстати, у GCC есть расширение — он все-таки позволяет создавать вложенные функции и их даже можно вернуть наружу (т.е. upward funarg). Достигается это за счет техники трамплинов (trampoline).

Также, если нет upward funarg'a (иными словами, если вложенная функция используется только локально внутри родительской функции, «не убегая» наружу вверх), то техника лямбда-поднятия (lambda lifting) также решает эту проблему. Именно она используется в Паскеле для вложенных функций.

Помимо этого, часть «первоклассности» (без вложенных функций) все же есть и в Си. Например, можно свободно передать указатель на функцию (к примеру, параметр функции сортировки). Поскольку Си использует также статическую область видимости, то функция правильно будет использовать переменных того контекста (глобального, т.к. нет вложенных функций), где она была порождена, а не контекста, где будет запускаться.

если каждый потомок будет содержать копии методов родителя, то это приведет к лишнему расходу памяти


Здесь стоит отметить, что «копии метода» не будет, но будет, действительно, копия слота (т.е. создастся родное свойство). Слот же этот будет ссылаться на ту же функцию. Примитивов, конечно же, это не касается.

Некоторые прототипные языки, например Io, используют так же делегирующее наследование, но метод, который осуществляет «сцепление» предка с потомком назван почему-то clone ;)

каждый конструктор «висит» над своим объектом


В общем случае, порожденный объект не зависит от своего конструктора.

Пара конструктор+прототип (в этом примере: B()+B.prototype) играет ту же роль, что класс в классическом ООП


Собственно, это и есть «класс» в JavaScript (только без сахара), поскольку реализует классовый code reuse. Как эта пара превращается в класс при добавлении сахара можно хорошо видеть, например, на классах CoffeeScript. Или Python. Да-да, Питон — это такой же делегирующий «прототипный» язык, как и JavaScript, только в нем добавлен сахар. Именно это я отмечал, говоря, что разницы «прототип vs. класс» недостаточно, всегда нужно задействовать все сопутствующие механизмы (первоклассные сущности, динамика и т.д. — здесь сводная таблицы — "классификация классов").

Этот пример с Python'ом достаточно интересен для анализа. Как только появляется «сахар» — всё, язык сразу же классовый и сразу же понятный. Но стоит убрать из Питона сахар — мы получим тот же JavaScript в этом аспекте. Таким образом, «сахар решает». А «класс» — это не какое-то ключевое слово class в языке, и даже не возможность создавать классы с помощью этого слова. Класс — это возможность программировать в классифицирующем стиле. А уж как это делается — с ключевым ли словом class или ключевым словом function — это по большому счету дело десятое.

наследование сделано по-старинке, через new


Такой же сахар над делегацией для классов (как в Python, Ruby или CoffeeScript), кстати, довольно активно обсуждался в es-discuss (возможно мы даже увидим классы в JS — такие же как в Coffee). Так что, «по-старинке» скорей всего здесь некорректно. Разница ведь в данном случае не в «старинках», а в методах code reuse'a. Если нам нужен классовый реюз (т.е. (1) возможность генерировать объекты по шаблону и (2) наследовать строгую иерархию), то мы используем классовый реюз. Если нет — пожалуйста, прототипный (т.е. (1) не нужно генерировать много однотипных объектов и (2) «наследую от кого хочу»). Это два разных код реюза и оба применимы. Здесь на Хабре был подробный тред в комментах на эту тему.

Ссылка на родительский объект (__parent__ в листингах 6 и 7) во многих реализациях называется __proto__


Не совсем удачно выбранное псевдо-имя, т.к. __parent__ — это реальное (хоть и нестандартное) свойство как раз, чтобы связываться объекты активации в scope chain. Больше подошло бы __delegate__ — оно отражает суть механизма и показывает, что по идее, может быть несколько прототипов (делегатов), если это свойство, например, будет списком. Так, к примеру, Io и Self поддерживают множественное наследование, как раз расширения список делегатов. Если, вдруг, станут интересны эксперименты, для JS тоже такое реализуется на прокси-объектах.

В случае вызова функции не как метода объекта, this по-умолчанию указывает на глобальный объект


Да, там есть сложные заморочки, когда, и вроде кажется, что функция вызвана как метод, но все равно this становится глобальным (или undefined в strict mode):

var o = {m: function() {this;}};

o.m(); // o
(o.m)(); // o

(o.m = o.m)(); // global
(o.m || o.n)(); // global


В целом же, еще раз — статья отличная ;)
> Дуглас Крокфорд — тут вобще не в тему

Ну, Крокфорд (официально) является членом TC-39. На практике «его» идеи тоже отражаются в ES (например, Object.create, JSON.parse, etc).

Насчет Yahoo, он мне сам сказал, что уже давно особо ничем там не занимается ("...nothing. Really."), ** пинает и разъезжает по конференциям :D
Предлагалось, обсуждалось — не будет принято, т.к. backward compatibilities рулят развитием JS и основной «лейтмотив» — «не ломать WEB» (т.е. то, что уже написано). Некоторые вещи все равно будут необратимо сломаны, но это уже в зависимости от «migration tax», т.е. что достойно, чтобы поломать, а что не так и важно (как например отрицательные индексы, без которых, в принципе, можно прожить; но с ними слаще, да ;)).
> но имхо уже через-чур глядеть в код и видеть отрицательный индекс массива (которого никогда не могло быть) и задавать себе вопрос «wtf?!»

Да ну, всего лишь сахар для более удобного обращения к индексам с конца: a[1] вместо a[a.length - 1].

И такая парадигма используется в разных языках — те же Ruby, Python. Хотите используйте (с сахаром), хотите не используйте (без сахара, с шумом).
Именно поэтому я и создал свою удобную обертку для работы с объектами, где все эти хуки можно навешивать простыми присваиванием в рантайме. См. выше п. 2 «Метапрограммирование на уровне объекта». Такая модель отражает, например, подход Python (откуда ES много чего позаимствовал).

Основная задача (по мнению TC-39), чтобы foo.get = 10 не означало, что ты «испортил» мета-get-хук. С другой стороны, почему «испортил», если ты сам за свои объектов и следишь — так и появляются обертки типа приведенных выше.

P.S.: кстати, уже ES5 подход с их статическими методами, типа Object.getPrototypeOf, Object.keys и прочее тоже именно из-за разделения мета- и прикладного уровня. Мне в свое время (год назад) этот подход тоже не понравился, но что сделано — то сделано, и с точки зрения разделения мета-уровня и «чтобы не испортить» это осмысленное решение и в целом правильное. Хотя, повторю, синтаксически получается много «шума», отступов и т.д.

P.S.[2]: ты еще не видел как будут value-proxy себя вести (это те, что для перегрузки операторов). Опять же — в Python это все на __magic__ методах, в ES куча синтаксического шума и телодвижений для этого. Причины — вновь разделение мета-уровня и оптимизация по памяти.
Молодец, что переводишь статьи / создаешь русскоязычные версии. Чтобы русское JS-сообщество не отставало — это полезное и хорошее дело.

По поводу прокси, от себя могу добавить несколько примеров, когда я экспериментировал с ними в самых первых имплементациях SpiderMonkey:

Из интересного:

1. Массивы с отрицательными индексами — как в Python:

var a = Array.new(1, 2, 3);

console.log(a[-1]); // 3
console.log(a[-2]); // 2

a[-1] = 10;
console.log(a); // 1,2,10
console.log(a[-1]); // 10

2. Мета-программирование на уровне объекта — фишки с __count__, __noSuchProperty__, __delete__, __get__, __set__ и т.д. Это только ради академического интереса (вообще, прокси и создавались, чтобы разделить мета- и прикладной уровни).

Можно менять трэп обычным присваиванием функции:

var foo = Object.new({
  __get__: function (name) {
    console.log('__get__:' + name);
  }
});

foo.bar; // __get__: bar
foo.baz; // __get__: baz

foo.__get__ = function (name) {
  console.log("New get: " + name);
};

foo.bar; // New get: bar

3. Сахар для разделенного мета-уровня

var foo = Object.new({
  // normal object level
  data: {
    x: {
      value: 10,
      writable: true
    }
  },
  // meta-level
  meta: {
    noSuchProperty: function fooNoSuchProperty(name) {
      console.log("noSuchProperty: " + name);
    },
    noSuchMethod: function fooNoSuchMethod(name, args) {
      console.log("noSuchMethod: " + name + ", args: " + args);
    }
  },
  // a prototype of an object
  proto: Object.prototype
});

foo.bar; // noSuchProperty "bar"
foo.bar(1, 2, 3); // noSuchProperty: "bar" -> noSuchMethod: "bar", args: 1, 2, 3

// normal-level doesn't disturb meta-level
foo.noSuchProperty = 10; // assign to normal-level, but not to meta-level
// still noSuchProperty of the meta-level is called
foo.baz; // noSuchProperty: "baz"

// the same with noSuchMethod
foo.noSuchMethod = 20; // assign to normal-level, but not to meta-level
// still the meta-level noSuchMethod is activated
foo.baz(10, 20, 30); // noSuchMethod: "baz", args: 10, 20, 30

4. Оптимизированный __noSuchMethod__ — функция-активатор реюзается.

5. Делегирующие примеси (как в Ruby) или множественное наследование. Используется именно реюз и делегация, а не просто копирование в собственные свойства.

И т.д. — больше можно найти здесь.

P.S.: был длинный тред с дебатами, где мы обсуждали, нужно ли добавить флаг isCall в get трэп — чтобы различать выражение вызова: foo.bar от foo.bar(), но пока решили не делать (что затрудняет имплементацию __noSuchMethod__ — нужна функция-активатор, которая является значением любого свойства).
Описание семантики объекта arguments в ES5/non-strict ES5 JS кодом: gist.github.com/539974

В Хроме помимо упоминавшегося бага, был еще баг с удалением индексов arguments.

Так же, учтите, что в strict-ES5 аксессор для индексов arguments больше не создается (т.е. обычные статические копии формальных параметров).

И, касательно Harmony (aka ES6 или ES.next) arguments вообще будет удален и заменен на rest — полноценный массив.
некий Аллен Вирфс-Брок

Так, к слову, Allen Wirfs-Brock — один из участников TC-39 (technical committee 39) — группа людей занимающаяся стандартом ECMA-262.

А вообще, приведенный код в этом топике, это не его версия, а Dave Herman'a (он тоже из TC-39). Версия Allen'a в его блоге описана.
Всё, я понял, где у нас «нестыковка» (ну, образно — на самом деле, и ты, и я правы).

Ты смотришь на это со стороны обособления и четкого разграничения (кстати, заметь, как бы забавно и странно не звучало, ты тоже именно классифицируешь, стратифицируешь JS от других). Тебя интересует, а как преподать это в большей мере новичкам, показать им эти «разграничительные классифицирующие теги» aka «смотрите, мой язык совершенно другой: тут нет того, тут нет сего».

Я же, действительно, размышляю об общих структурах и закономерностях, которые прослеживаются во всех языках. Я действительно размываю эти рамки, потому что вижу в них только конкретику отдельно взятой реализации; реализации общих теоретических схем, ещё раз повторю, разработанных идеологами безотносительно каких-то там ECMAScript'ом, Jav(a), C, CoffeeScript'ов, Python'ов и т.д.

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

показать его особенности, его своеобразие, его красоту

Воот. Вот именно здесь, я понимаю, «немного печально», «что мой язык кем-то вдруг размывается и смешивается с »немоими" языками".

Да, всё верно. Стояла бы задача «выделить терминологические и идеологически различия и рассказать о них новичкам» — вперёд, никто бы не стал ничего «размывать».

Я же подхожу к этому вопросу (и начал с этого) с точки зрения выделения именно этих общих закономерностей, сглаживая по большому счёту (а так ведь интересней играть ;)) нюансы конкретики. И одной из мои целей является объяснение, что «классы не нужны» — это просто несправедливое (да и чего уж там — невежественное) вещание. Но, которое прощается новичками.

Поэтому да, если хочешь показать именно обособленности новичкам — конечно удобно выделять их. Если мы беседуем в рамках теории языков программирования, их семантики и дизайна — есть «second-class static classes» и есть «first-class dynamic classes» (с возможными вариациями, например, «first-class static classes» — которые «замороженные»), и каждая технология, стратегия может быть использована в отдельно взятом конкретном языке.

«классы в языке — это возможность генерировать и классифицировать»… ну, тогда везде они… классы эти.

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

P.S.:

всё-таки продолжаю бороться со сложностью — классов в js нет ;-)

Ну, либо так — тоже избавление от сложности. Только с альтернативной точки обзора ;)
В языке нет «second-class static classes» (по твоему определению — «ноль жёстко заданных связей и поведения»). Естественно, и очевидно, что таких классов нет и не было (ну а как? — везде динамика и всё «first-class» — это «физически» невозможно). Здесь всё верно.

А вот, ещё раз, «first-class dynamic classes» — есть и всегда были. Только без сахара.

Здесь, либо мы тогда Python и CoffeeScript должны называть только делегирующе-прототипными, либо, всё-таки (и правильно) — допускать, что в таком же (делегирующе-прототипном) языке мы можем выделить понятие «класса» как (повторю) «возможность генерировать и классифицировать».

Ведь это ж не просто какая-то «теоретически-идеологическая хрень от неча делать». Я специально расписал выше полный путь «от» и «до», чтобы показать, что понятие «классифицированной генерации» возникает в результате самой обыкновенной и практической «борьбой со сложностью».

Перестала нравится «копи-паста» при генерации однотипных объектов — всё, убрали (инкапсулировали) её в функцию; избавились от этой «сложности».

Хочется классифицировать по тегу, но не хочется занимать много памяти, храня одну и ту же запись в самом объекте — всё, сделали вспомогательный спец.объект-прототип и задействовали делегацию (с виду вроде свойство «родное», а на самом деле хранится в одном месте). От этой «сложности» тоже избавились.

Надоело каждый раз вручную ставить прототип (и все остальные приблуды, типа return'a и т.д.)? — всё, сделали «за кадром» это действие.

Т.е., видишь, да — самые обыкновенные практические задачи-причины.

В итоге, можно остановиться на каком-то определённом этапе. А те, кто увидят ещё какую-то сложность, могут дальше от неё избавиться. Захотелось обособить функцию классифицирующей генерации от обычной — всё, заменили слово «function» на «class» в грамматике. Но раскладывается-то («десахарится», desugars) оно внутри всё равно на эту же самую «function» — jashkenas.github.com/coffee-script/#classes

И т.д., и т.п.

Не захотели делать mutable объекты — пожалуйста, заморозь (freeze) их. Кстати, если мы избавимся от динамики и изменяемости — мы подпадём под твоё определение «класса» с «ноль жёстко заданных связей и поведения»? Кстати, цитата Brendan'a, вырванная из контекста, тоже говорит лишь об изменяемости, т.е. о «second-class static classes», которых, естественно, не было в ES3. Но в ES5 с Object.freeze — пожалуйста, убери изменяемость, если хочется.

После заморозки — не захотели делать всё это first-class, пожалуйста, сделай second-class (не будут у тебя классы объектами, а просто — «статические лепёшки» в коде, которые могут генерировать и классифицировать объекты — точно так же, как могли до этого — обычные «first-class dynamic functions»).

Т.е. разницы нет как классифицировать — с ключевым ли словом «function» или с ключевым словом «class». Главное — это сами причины и нужды и, как следствие, методика, стилистика. А синтаксис — это уже даже не десятое, а сотое дело.

P.S.: в самом «низу», на уровне ячеек памяти, в абстракции, она тоже классифицирована (типизирована). Как, думаешь, различается указатель на int или указательно на float. Или, что, например, этот блок памяти может хранить int — ведь ячейки «все равны»? Точно так же — по классификационному тегу. Есть ячейки данных, и есть специальная ячейка, которая хранит тип/класс того, что в неё кладут — для проверки.

P.S.[2]: Ещё раз — разница между «классовое» и «прототипное» только идеологическаяклассифицированный реюз и генерация и неклассифицированный (хаотичный, прототипный) реюз и генерация. Технически же, это может быть одна и та же система.
… связка «конструктор + протоип» с классифицирующим стилем программирования.
1
23 ...

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Date of birth
Registered
Activity