Pull to refresh
1
0
Dmitry Soshnikov @dsCode

User

Send message
> Стиль программирования решает.

Однозначно.

> ноль жёстко заданных связей и поведения

Воот. Во всяком случая, я уже вижу, что ты подразумеваешь под словом «класс». И это именно то, о чём я упоминал выше — «second-class static classes».

Однако, ты (да и Brendan) с какой-то стати отмёл «first-class dynamic classes».

Я поясню терминологию, чтобы не было путаницы (не для тебя, а для тех, кто возможно не в курсе).

— первоклассная (first-class) сущность — такая, которая может быть использована в качестве обычных данных (создана литерально в рантайме, передана в качестве аргумента функции, возвращена из функции и т.д.);

— второклассная (second-class) сущность — соответственно, та, которая не может быть использована в качестве, описанном выше.

Дальше. Динамически изменяемая (dynamic mutable) сущность — понятно, в рантайме расширяй объект сколько хошь. Static (или даже static immutable) — соответственно, наоборот.

Так вот эти понятия с понятием классификации по большому счету вообще никак не связаны. По «менее большому счету» — мы можем лишь принять second-class static систему с возможностью генерировать объекты и классифицировать их. Конечно же, никакие фичи делегации, динамики и прочего из JS здесь не будут действовать.

Но если мы возьмем делегирующую first-class dynamic систему с «сахаром», то мы тоже получим понятие класса. При этом мы свободно сможем дописывать в рантайме новые слоты, добавлять новые методы, менять класс/прототип объекта динамически, передавать класс в качестве аргумента и т.д. dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/#dinamicheskaya-klassovaya-organizatsiya

Так вот, почему, если Python (или CoffeeScript) делегирующие языки, их называют классовыми? Потому что в них всего лишь по-дефолту, «из коробки» уже написан этот сахар, эта обёртка с использованием ключевого слова class. Но внутри-то они — абсолютно те же, что и JS.

Еще раз, все знают, что JS — прототипный язык (ладно, я тоже, допустим, знаю). Но при этом в нём, как и в любом делегирующем языке, можно программировать в классовом стиле. И да, именно стиль решает. А точнее не стиль, а понятие «возможность генерировать и классифицировать» — вот, что называется в абстракции «класс». А конкретные реализации — это уже дело десятое. web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html
Язык программирования — для программиста. Необходимость в языке программирования и программировании в целом — это, повторю, борьба со сложностью. Чем более высоко абстрактен язык, тем эта борьба становится легче — путём человек-приближенных, высоко-абстрактных понятий ты описываешь ту или иную систему.

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

Опять же, ООП всего лишь теоретическая парадигма разработанная идеологами задолго до реализаторов движков и языков. Её удобно применять для решения конкретных задач — и именно где требуется (либо просто удобно) классификация структур по определенному признаку. В других задачах ООП может и не требоваться.

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

В некоторых языках (Java, C++, др.) класс напрямую связан с понятием типа. Но в общем же случае, для того, чтобы иметь возможность классифицировать, нужен лишь классификационный тег (classification tag). Иногда называют тегом типизации (type tag) или, опять совсем просто — типом. Это некоторое свойство (или набор свойств в общем случае), которое позволяет однозначно определить объект твоей классификации от объекта с таким же набором свойств.

Как я отметил выше, первая главная операция в ООП — это возможность порождать (construct) объекты. Если мы примем, что объект — это просто «набор свойств и поведений», то синтаксически мы можем прям так эти свойства со значениями и описывать:

var point = {x: 1, y: 2};


Если нам нужно три «точки», то:

var a = {x: 1, y: 2};
var b = {x: 3, y: 4};
var c = {x: 5, y: 6};


Но очевидно, что повторное использование кода (code reuse) методом «copy-paste» — это примитивная методика программирования (что если нам понадобится 100, 1000 точек?). Поэтому нам нужен некий удобный сахар для генерации объектов со схожей структурой. И самый простой способ сделать это — это использовать обычную функцию (да-да, точно так же, как мы всегда делаем, когда код повторяется больше двух раз — так называемое «правило двух» — «если код повторяется больше двух раз, вынеси (инкапсулируй) его в функцию».

function makePoint(x, y) {
  return {
    x: x,
    y: y
  };
}

var a = makePoint(1, 2);
var b = makePoint(3, 4);
var c = makePoint(5, 6);


Преимущество очевидно — если в дальнейшем детали реализации точки изменятся, нам нужно будет поменять код только в одном месте. Кстати, именно это и называется инкапсуляцией, и именно в этом её основная суть — отделение уровня реализации точки и уровня использования точки. Если уровень использования не будет трогать внутренности реализации, то реализация может меняться хоть каждый день — при том же внешнем интерфейсе для использования.

Так вот, основная цель конструктора, как показано выше — это всего лишь удобный код реюз — генерация однотипных объектов.

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

Например, это может быть имя функции, которая создала объект. А лучше — ссылка на неё. Можно поместить этот тег прямо в самом объекте, но очевидно, что можно выделить вспомогательный спец. объект и задействовать делегацию — так, тег будет храниться в единственном экземпляре и будет доступен всем нашим объектам. Назвать его можно прямо и в лоб — constructor (вспомогательный объект при этом назовем prototype):

makePoint.prototype = {
  constrcutor: makePoint
};


И модифицированный способ создания:

function makePoint(x, y) {
  return {
    __proto__: makePoint.prototype,
    x: x,
    y: y
  };
}


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

a.constructor // a.constructor -> not found, a.__proto__.constructor -> found == makePoint.


Разработав всё это (нашу фабрику «makePoint» по генерации объектов-точек), мы придём к выводу, что некоторые вещи можно оптимизировать, убрать сложность (мы ж со сложностью боремся!):

1. уберём (да, для сахара) этот префикс «make», заменим его ключевым словом «new»;

2. сделаем return опциональным (правда, на кой ляд он нам в общем случае? — пусть система сама знает, что надо вернуть объект);

3. будем создавать вспомогательный объект автоматически за кадром и устанавливать __proto__ тоже «по-тихому» (ты хочешь повторять это ручками каждый раз? — я нет);

4. введём какое-нибудь специальное слово (например, this), чтобы ссылаться на созданный объект и не писать каждый раз эти скобочки { и }. Хотя, тут двояко, иной раз со скобками удобней.

5. что-нибудь ещё придумаем…

В итоге, мы получим систему конструкторов в JS:

function Point() { // no "make"
  this.x = x;
  this.y = y;
}

var a = new Point(1, 2);
var b = new Point(3, 4);
var c = new Point(5, 6);

a.constructor // Point


И это именно процесс генерации и классификации. Вот то, что описано выше — от и до — это есть класс. И в JS, да, он реализован вот в таком виде. Дальше, пожалуйста, добавляй сахар наследования (связывай вспомогательный объект-прототип с другим объектов по тому же механизму делегации) и т.д.

Технические хаки (типа «я щас подменю свойство constructor» и т.д.) — меня, как программиста, вааапще не касаются. Удобно мне так классифицировать объекты — я классифицирую. Не буду я ничего ломать, подменять constructor, использовать поломанный [[Class]] в каком-нибудь движке и т.д.; я буду просто программировать в классифицирующем стиле. Если мне надо.

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

В итоге, в JS (как и в любом другом языке с делегацией) можно программировать как с классами, так и без них. Но в классовой системе нельзя программировать в бесклассовом режиме.
Нет такого словосочетания «proper classes». Тот, кто написал это в ES3 спецификации имел в виду «second-class (static) classes». «Классы» в JS ничем не отличаются от классов в Python. И как они устроены изнутри, можно хорошо видеть на примере сахара CoffeeScript.

Ещё раз — возможность классифицировать — вот что такое «классы». И у JS всегда была (и есть) эта возможность. Здесь можно реюзать код «хаотично» (прототипно) и систематизировано (классово). Всё остальное — это уже дополнительный идеологических сахар.

Основное в ООП это:

1. возможность генерировать объекты (т.е. всего лишь комплексные структуры с некоторым набором полей) — constructors.

2. осуществлять выборку значений этих полей — selectors.

Всё. Остальное: реюз, классификация, наследование, приватные свойства (для разделения уровня абстракций, но никак не для параноидального сокрытия) — это всё дополнительные сущности.

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

Так что, в JS есть классы и всегда были. Но, да, без сахара ;) Хотя, конечно, эти размышления не для новичков.

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

Я ж говорю, сам ES использует понятие класса для классификации объектов — свойство [[Class]], так почему же его бояться и относить к ненужному? Повторю, оно не нужно, если я хочу просто зареюзать код от другого объекта. А когда мне нужно классифицировать объекты — зачем мне отказываться от классов?

P.S.: меня интересует знание вопроса из самой глубины, а не просто цитаты конкретных людей из TC-39, которые так же корректируют спецификации, ошибаются и т.д.
В ECMAScript нет (пока) сахара для классов. Но возможность классифицировать (т.е. способоность создавать объекты по заданному шаблону и с нужным классификационным тегом) там была всегда.

Основная разница классового и прототипного ООП (кто там жаловался на слово «идеология»? ;) именно идеологическая.

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

С точки зрения цепи наследования большой разницы нет:

Прототипное: own -> proto -> proto -> proto -> null
Классовое (абстрактно): own -> class -> superclass -> superclass -> null.


При этом, в прототипном (делегирующем) программировании, цепь наследования так же линейна.

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

Наличие сущности „класс“ в языке не отменяет возможности классифицировать объекты. С этой точки зрения, например классы Python'a — это всего лишь синтаксический сахар того же делегирующего наследования, которое используется в ECMAScript. Для этого класс, естественно, должен быть сам первоклассным объектом. Здесь классы делятся на:

— Первоклассные динамические (first-class dynamic classes); примеры: Python, Ruby, CoffeeScript, ECMAScript (без сахара);

— Второклассные статические (second-class static classes); примеры: C++, Java.

Так вот, JS всегда имел „first-class dynamic classes“ в виде пары „конструктор + прототип“. Т.е. конструктор умеет генерировать объекты и задавать им классифицирующий тег (например, свойство constructor). При этом, как классифицировать объект — с сахаром ли (ключевое слово class) или без него — это уже второстепенный вопрос.

Самое интересное, что ECMAScript сам использует понятие „класса“ для классификации своих объектов. И, не удивительно, использует классифицирующий тег с таким же именем — [[Class]].

var a = {};
var b = [];

// both are objects
alert([typeof a, typeof b]); // "object", "object"

// but have different classification tags
var getClass = Object.prototype.toString;


alert([getClass.call(a), getClass.call(b)]); // "[object Object]", "[object Array]"


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

Если нужно просто повторно зареюзать код от другого объекта — зачем мне класс — используйте Object.create. Если я решил, что у меня должно быть несколько объектов одной классификации (например, компонент „кнопка“ в UI системе) — с какой стати мне отказываться от классов?

P.S.: недавно я отвечал в подобной теме:

www.mail-archive.com/jsmentors@googlegroups.com/msg00513.html
www.mail-archive.com/jsmentors@googlegroups.com/msg00503.html (примерное начало темы).

P.S.[2]:

— Есть ли в JS классы?

— Сахара нет, но классы (т.е. возможность классифицировать) — есть и всегда была. А сахар допиливается обёртками (так же, как и Python, так же, как и в CoffeeScript).

P.S.[3]: jashkenas.github.com/coffee-script/#classes
Хотя, Chrome выпендривается с одной <. Вернул <<.
например, +XXX вернёт число

В общем случае, это ToNumber (вызывается плюсом) вернёт число; valueOf может вернуть объект (так, например, возвращает стандартный Object.prototype.valueOf — результатом будет this-value).

var o = {};
o.valueOf() === o; // true

Ты можешь переопределить valueOf любого объекта и вернуть всё, что надо. Только если оба — и toString, и valueOf будут возвращать не примитив, а объект, тогда может быть TypeError в некоторых операциях (например, в конвертации ToNumber). При этом, они (до ошибки) всё равно будут вызываться.

function foo() {}

//console.log(foo.valueOf() === foo); // true

foo.valueOf = function () {
  console.log("foo.valueOf is called");
  return {};
};

foo.toString = function () {
  console.log("foo.toString is called");
  return {};
};

+foo;

-->

"foo.valueOf is called"
"foo.toString is called"

TypeError "can't convert foo to number"

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

foo.valueOf(); // OK, объект
foo.toString(); // OK, объект
Ничего не мешает, так же valueOf вызовется. В моей версии — одинарная в примере.
Да, я в курсе, но тем не менее.

оставив от него только идею


«Только идею»? ;) Идея — первична и ядро всех новаторских вещей. Реализация — дело десятое. Удобная реализация — это хорошо.
В сущности, да — «шило на мыло» — не важно, написать в обертке "_extends" или "<". Здесь лишь можно отметить идейную вариацию с задействованием valueOf. А по поводу упрощенного «DSL» — да, мне больше Coffeescript нравится, хотя я его и не использовал.

P.S.: автору статьи: кстати, идея и изначальная реализация принадлежит Tobias Schneider (и у него заведен репозиторий для этого проекта — github.com/tobeytailor/def.js). И, хоть это и написано в копирайте скрипта, большинство, почему-то, упоминают John Dalton.
Вообще, Рубевый «сахар» наследования — это < одна угловая скобка ;)

То же (+ добавлен this.base() через caller): github.com/DmitrySoshnikov/def.js Но, т.к. caller — deprecated и бросает исключение в strict mode, то можно переделать на wrapping функциями.
def a
  yield
end
a{ return  0 } # => LocalJumpError: unexpected return

Это относится к ключевому слову return. Оно, ведь, не просто опционально (как пишут некоторые статьи); в определённых местах эта опциональность имеет смысл:

def a
  yield 10
end
a {|n| n + 10 } # 20
> а заложено в язык

Точнее — в идеологию. И эта идеология присуща многим языкам. Просто большинство кроме статично-классового ООП и не видали ничего больше. Естественно, легче назвать это «религией» и «откреститься» от этого.
Причём здесь Ваше отношение к альтернативной идеологии ООП и «религия»? Python использует делегирующую модель наследования с объектами первого класса. Абсолютно та же картина в Javascript.

По поводу «private» и «protected» — у Вас, опять же, неверное восприятие. Инкапсуляция — это усиление абстракции, а не параноидальное сокрытие от «злых хакеров». Использовать сокрытие ради сокрытия — это большая и самая распространённая ошибка, связанная с неверным восприятием инкапсуляции. Тогда как, в первую очередь — это помощь программисту, более абстрактно описывать систему.

В Ruby тоже можно свободно получить доступ (если того захочет сам программист) к инкапсулированным данным.

Найдите время, прочитайте полностью: dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/
100.toString.length — синтаксическая ошибка, должно быть 100..toString.length: первая точка определяется, как отделение дробной части, вторая — уже доступ к свойству.
Хотя, Zakas опубликовал пост с ответами на мой quiz: www.nczonline.net/blog/2010/02/23/answering-soshnikovs-quiz/ (хотя, я вижу две ошибки в объяснении, одна незначительная (на #3), другая — очень (на #6)).
Zakas-у, вероятно, просто сообщили об этом. В любом случае, повторю, мне эта позиция («этот красивый комментарий публикую сейчас, а этот нет, а этого — вообще не публикую») не близка. Удалять спам и троллинг — это, конечно, совершенно, другое, хотя, я с этим не сталкивался в своих статьях. А Ваш ответ звучит так, как будто, Zakas «сделал одолжение и соизволил», что ещё больше является забавным пафосом, который меня всегда «умиляет» в людях.

Насчёт последнего комментария, этот человек (Asen Bozhilov) тоже говорил, что Zakas модерирует полезные, но не/мало/лицеприятные комментарии: groups.google.ru/group/comp.lang.javascript/browse_thread/thread/2b9dcc0b88ff8ffa?hl=en#9255f32d6271e3bd
Мой коммент был 5-ый, перед комментом kangax-a, который уже опубликован ;) Ладно, проехали, мне от этого ни тепло, ни холодно. Это PR и не больше. Вернее, боязнь анти-PR-а.
В принципе, здесь поля для беседы не было, я просто отметил, что тест — слабее, тех которые он упоминает в описании теста, в любом случае, поблагодарил за работу. Возможно, он счел это не нужным, ни о чем. Мне без разницы. Я ни один (ни один.) коммент никогда не скрывал. Объективная оценка — это главное. Я не опускал тест (а он подумал, скорей всего, именно это), ни говорил, что он плохой, что он тупой, что он не нужный, еще что-то. Я просто отметил, что он по уровню проверки знаний — слабее, чем те, которые он сам же и упоминает.

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

> Я его ловил, указывал на ошибки, премодерация меня не удавила

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

Ладно, хрен ты с ним.
Да, и мне, собственно, по барабану, прошел мой коммент модерацию или нет, я правил не нарушал, не троллил, не был rude (как у него написано в Note: к комментам), говорил по делу. Но понял, что для этого человека в первую очередь важен PR, а не реальное положение дел, «наука» (ECMAscript) и объективные оценки.
> Ваш коммент не прошел модерацию? Интересно, что же Вы написали.

Примерно (с переводом): «Объективно, этот quiz легче, чем те, которые упоминаются (в целом, эти quiz-ы не требуют каких-то глубоких анализов); уровень сложности — 3, у kangax-a был на 4, но в любом случае — спасибо.»

Information

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