LINUX.ORG.RU

Два подхода к контекстам

 , ,


4

2

В этом посте я собираюсь рассмотреть различия в объектной модели Smalltalk и CLOS и как эти модели связаны с понятием контекста. Поклонники статической типизации могут не читать. С открытием CLOS возникли споры о том, CLOS — это что? ООП или не ООП? Становление новой науки неизбежно приводит к терминологическим спорам. ООП — термин расплывчатый и ИМХО, его следовало бы избегать. Как CLOS, так и Smalltalk реализуют одну важную фичу — ad hoc полиморфизм. Эта фича крайне важна для программирования, т.к. позволяет настраивать уже существующий код без изменения его исходного текста. Модель Smalltalk имеет ограниченный ad hoc полиморфизм, т.к. фактически позволяет производить диспетчеризацию лишь по одному аргументу. Однако, кроме ad hoc полиморфизма есть еще одна вещь, связанная с ООП — инкапсуляция. Итак, кратко опишем две ОО модели:

  • Инкапсуляция и ad-hoc полиморфизм (Smalltalk).
  • Ad-hoc полиморфизм без инкапсуляции (CLOS).

Далее я покажу, что эти два подхода противостоят друг другу. В Smalltalk объект — самодостаточная сущность. Чтобы распечатать объект (получить его внешнюю репрезентацию) необходимо послать объекту соответствующее сообщение. Это означает, что внешняя репрезентация объекта зависит только от него, и в минимальной степени зависит от внешнего контекста вызова. В CLOS внешняя репрезентация объекта целиком и полностью зависит от текущей обобщенной функции print-object. Теоретически у одного экземпляра Lisp системы может быть много различных обобщенных функций print-object.

Обычно естественные языки имеют лишь одно текстовое представление. Это не так для иероглифических языков, и скорее всего мы придём со временем к ситуации, когда один и тот же язык будет иметь множество проекций на текст. К этому же идет и эволюция языков программирования. Так, в Perl 6 функция load принимает на вход грамматику, которая описывает Perl 6 и написана на Perl 6. Далее свойство независимости от внешнего представления мы будем называть синтаксической абстракцией. ЯП, наиболее полно поддерживающий синтаксическую абстракцию — Lisp. Программы на Lisp записываются в виде S-выражений, но скобки тут нужны только для того, чтобы указать ридеру структуру. В Lisp текстовая репрезентация программы называются выражениями, а вычислитель работает не с выражениями, а с формами. Термин форма подчеркивает абстрактную природу вычислителя. Я ранее уже писал о том, как можно усилить синтаксическую абстракцию символов в Lisp.

Итак, синтаксическая абстракция предполагает, что объект, помещенный в различные контексты может иметь различные внешние представления. Модель Smalltalk не поддерживает синтаксическую абстракцию, т.к. объект сам должен вернуть свою репрезентацию в ответ на соответствующее сообщение. Давайте теперь обобщим эти рассуждения оторвав их от печати и считывания, на примере которых понятно что, инкапсуляция означает контекстную изоляцию.

Контекст — крайне важное понятие. Игнорирование контекста и абсолютизация понятий приводит к проблемам. Как определить тип объекта? Широко известен спор между Платоном и Диогеном. «Человек, - сказал Платон, - это двуногое животное без перьев». Тогда Диоген ощипал петуха и со словами: «Вот твой человек». Платону пришлось сделать уточнение: «Двуногое животное без перьев и имеющее ногти». Понятно, что тип объекта зависит от наблюдателя. Языки программирования начали с простой идеи — к каждому объекту прилеплен бейджик с его типом. В Smalltalk человеком является тот, кто на вопрос «ты кто?» отвечает — человек. Какой может быть типизация, которая учитывает контекст? Она носит название предикатная типизация. Еще иногда эту идею называют утиной типизацией. В этом подходе тип объекта зависит от того, кто и какие вопросы задает объекту. Платон может определить человека по отсутствию перьев и наличию ногтей, а Диогену нужен фонарь, чтобы определить, кто есть человек.

Одна из наиболее важных идей в истории программирования — гомоиконность является примером переключения контекста. В Lisp нет специально выделенного типа для AST. Является ли определенное дерево AST или нет, зависит от намерений исполнителя. Благодаря этому стало возможным метапрограммирование без значительного усложнения языка. Язык, который выделяет отдельный тип для AST должен иметь весь набор селекторов и конструкторов для этого типа, тогда как представление AST формами дает возможность пользоваться общими селекторами и конструкторами.

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

Ответ на: комментарий от komputikisto

Речь тут идет не непосредственно о CL, а о CLOS, так что не надо. в одном случае, реализуется пятью строками кода, а в другом — подключением монстра. это к вопросу о мощности языков.

J-yes-sir
()

Как CLOS, так и Smalltalk реализуют одну важную фичу — ad hoc полиморфизм

Обкурился? ad hoc полиморфизм это перегрузка функций и к ООП не имеет никакого отношения. ООП - это полиморфизм подтипов.

no-such-file ★★★★★
()
Ответ на: комментарий от komputikisto

Любая достаточно сложная программа на Си или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp.

rules={
   numberstring: 1,
   stringnumber: 1,
   numberboolean: 2,
   booleannumber: 2
}
Object.prototype.dyspatch=function(x){
   return rules[((typeof this.valueOf())+typeof x)]
}

^^^достаточно СЛОЖНАЯ ПРОГРАММА^^^

CLOSE HERE

^^^достаточно ПРОСТАЯ ПРОГРАММА^^^

anonymous
()
Ответ на: комментарий от komputikisto

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

https://github.com/Kalimehtar/gls

«CLOS» на предикатах. Иерархия определяется так: (and? type1 type2 ... typeN) является подтипом для type1, typ2, ..., typeN, а (or? type1 type2 ... typeN) является надтипом для type1, typ2, ..., typeN.

и не нужно никаких классов.

monk ★★★★★
()
Ответ на: комментарий от ilammy

иерархическую организацию подтипов объектов прекрасно описывают классы

Плохо описывают. Нельзя сделать подкласс от целого числа. Нельзя сделать подкласс «строка до 255 символов». Или «незакрытый файл».

monk ★★★★★
()
Ответ на: комментарий от monk

и не нужно никаких классов.

Спасибо за наводку! Вот как раз я так и думаю, что классы — лишнее. Структуры нужны, но не более.

komputikisto
() автор топика
Ответ на: комментарий от monk

Плохо описывают. Нельзя сделать подкласс от целого числа. Нельзя сделать подкласс «строка до 255 символов». Или «незакрытый файл».

Это смотря в каком ЯП


SubStr=function(value){
   this.value=value
}
SubStr.prototype=Object.create(String.prototype)
SubStr.prototype.showMe=function(){alert(this.value)}

foo=new SubStr("foo")

alert("foo".slice(1))
alert(foo.value.slice(1)) // унаследовали
foo.showMe() // свой метод
"foo".showMe() // fuck

J-yes-sir
()
Ответ на: комментарий от monk

Вот эти случаи не нельзя, а просто неудобно. Надо руками прописывать ограничения на состояние объектов подкласса во всех-всех его методах. Навешивание предиката делает это автоматически.

Вот что точно нельзя сделать наследованием, но можно предикатами — так это известная проблема круга и эллипса. Нельзя отнаследовать круг от эллипса, потому что методы эллипса могут нарушать контракт круга. Зато круг очень естественно представляется как (and? <ellipse> equal-axes?) — и круги автоматически теряют свою «круглость», если их изменят соответствующим образом, но при этом они остаются подтипами эллипсов.

Но и подклассы — это не подтипы. Так что неудивительно, что наследование не может представить вообще весь спектр отношений между типами. Но диспетчеризация по классам реализуется эффективнее предикатной, поэтому-то и классы и используются.

ilammy ★★★
()
Последнее исправление: ilammy (всего исправлений: 1)
Ответ на: комментарий от komputikisto

Вот как раз я так и думаю, что классы — лишнее

Классы нужны в качестве фабрик объектов, построения цепочек наследования, так тупо, удобней писать код. Другое дело, что классы не нужны в том виде, в котором они в плюсах, жабе, лиспе и прочих. А в смолтоковских, джаваскриптовских классах нет ничего плохого, они достаточно гибки.

J-yes-sir
()
Ответ на: комментарий от ilammy

потому что методы эллипса могут нарушать контракт круга

Такие вещи волнуют только быдло. Маша Пупкина унаследовала от отца Васи белые волосы, но не отнаследовала большие уши, отнаследовав маленькие, у мамы. Определенно, она нарушила контракты династии Пупкиных.

J-yes-sir
()
Ответ на: комментарий от ilammy

Никто не мешает объекту быть экземпляром нескольких классов. Причем, если выкинуть быдляцкую статику, проблема ромба исчезает.

J-yes-sir
()
Ответ на: комментарий от ilammy

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

ilammy ★★★
()
Ответ на: комментарий от J-yes-sir

Товарищ J-yes-sir (или terminator-101), прекрати! Проблема эллипса --- не проблема ромба!

Такие вещи волнуют только быдло.

Расскажи, пожалуйста, какие нынче вещи волнуют элиту?

komputikisto
() автор топика
Ответ на: комментарий от ilammy

Если следовать твоей логике, то нет. Потому что, как минимум, твои ограниченные числа нарушают контракт «включает в себя все числа (любое число)»

J-yes-sir
()
Ответ на: комментарий от komputikisto

Это все из той же песни, по-моему. И я говорил про проблему ромба, а не эллипса. Просвяти, в чем принципиальная разница?

J-yes-sir
()
Ответ на: комментарий от J-yes-sir

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

J-yes-sir
()
Ответ на: комментарий от komputikisto

Это ты написал?

Да. Но изначальная реализация не моя. Там же написано «основано на https://github.com/gregsgit/glos ». У него реализация для scheme48. Я адаптировал для Racket и чуть улучшил производительность.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от ilammy

Вот что точно нельзя сделать наследованием, но можно предикатами — так это известная проблема круга и эллипса. Нельзя отнаследовать круг от эллипса, потому что методы эллипса могут нарушать контракт круга. Зато круг очень естественно представляется как (and? <ellipse> equal-axes?) — и круги автоматически теряют свою «круглость», если их изменят соответствующим образом, но при этом они остаются подтипами эллипсов.

Псевдокод

Ellips=inherit Round
Ellips.extend equal-axes=false // деструктивное присваивание
ellips=new Ellips // nevermind

J-yes-sir
()
Ответ на: комментарий от monk

изначальная реализация не моя

Я прочитал. Все равно круто! Спасибо большое за труд! Но меня слегка удивило, что ты этим занимаешься. Как я понял, ты был против рестартов и CLOS и за подход Scheme. Только недавно с тобой обсуждали CL vs Scheme. Или я что-то не так понял?

komputikisto
() автор топика
Ответ на: комментарий от J-yes-sir

И точно также наоборот

Round=inherit Ellips
Round.extend equal-axes=true // деструктивное присваивание
round=new Round // nevermind

J-yes-sir
()
Ответ на: комментарий от komputikisto

Как я понял, ты был против рестартов и CLOS и за подход Scheme. Только недавно с тобой обсуждали CL vs Scheme. Или я что-то не так понял

Не против рестартов, а за прямое использование продолжений и исключений вместо них.

И GLS родился как CLOS без классов. Нормальный CLOS имеет проблему модульности (ответственности за интерфейс). Если единицы — классы, то их методы должны определяться вместе с ними. Но обобщённые функции взаимодействия между двумя классами не могут принадлежать ни одному из них.

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

GLS считает единственной сущностью обобщённую функцию. Иерархия есть у методов функции, а не у классов.

----

С другой стороны, на практике GLS мне ещё нигде не пригодилась. Она всё-таки добавляет накладные расходы, в отличие от классической системы классов. Но если мне вдруг понадобится паттерн Visitor, то я лучше использую GLS, чем буду городить Visitor на классах.

monk ★★★★★
()
Ответ на: комментарий от ilammy

Если круг не может быть подтипом эллипса, то и ограниченные числа не могут быть подтипом всех чисел (есть группа diag(p, q) ~ R^2 масштабирования для эллипсов и её подгруппа diag(r, r) ~ R для кругов, так же есть \x. x + k (для какого-то множества k) для целых чисел и её подгруппа для чётных).

motto
()
Ответ на: комментарий от motto

Если круг не может быть подтипом эллипса, то и ограниченные числа не могут быть подтипом всех чисел

Если круг/эллипс не могут изменяться методами, то круг является подтипом эллипса.

То есть должно быть

class ellipse()
{
   ellipse scale(double p, double q); // возвращает масштабированный эллипс
}

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

К слову для GLS проблемы нет.

(defmethod (scale! (r <ellipse>) (p double?) (q double?))
   (set-axis1 r (* p (axis1 r)))
   (set-axis2 r (* q (axis2 r))))

Будет прекрасно работать и с объектом, который удовлетворяет типу (and? <ellipse> equal-axis?). Просто после выполнения данного метода объект перестанет быть кругом (что с точки зрения классо-ориентированных языков нонсенс: метод не может изменить тип объекта).

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Будет прекрасно работать и с объектом, который удовлетворяет типу (and? <ellipse> equal-axis?). Просто после выполнения данного метода объект перестанет быть кругом (что с точки зрения классо-ориентированных языков нонсенс: метод не может изменить тип объекта).

А как с наследованием? Если, допустим, мы реализуем некоторый объект, который был экземпляром А и после вызова этого метода стал экземпляром B. Будет ли он наследовать от B после этого?

J-yes-sir
()

В Lisp нет специально выделенного типа для AST. Является ли определенное дерево AST или нет, зависит от намерений исполнителя. Благодаря этому стало возможным метапрограммирование без значительного усложнения языка. Язык, который выделяет отдельный тип для AST должен иметь весь набор селекторов и конструкторов для этого типа, тогда как представление AST формами дает возможность пользоваться общими селекторами и конструкторами.

В Scheme отдельный тип для AST (syntax), метапрограммирование там возможно, а всё усложнение метапрограммирования связано с гигиеной.

А подход «зависит от намерений исполнителя» привёл в Common Lisp к огромной проблеме с копированием и сравнением. Ещё можно вспомнить nil, который может в зависимости от контекста означать пустой список, отсутствие значения и ложное значение.

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

Думаете, это хорошо?

monk ★★★★★
()
Ответ на: комментарий от J-yes-sir

А как с наследованием? Если, допустим, мы реализуем некоторый объект, который был экземпляром А и после вызова этого метода стал экземпляром B. Будет ли он наследовать от B после этого

Разумеется.

(defmethod (scale! (r <ellipse>) (p double?) (q double?))
   ...)

(define <circle> (and? <ellispe> equal-axis))

(defmethod (display-radius (r <circle>))
   ...)

(define d (make-ellispe 1 2))
(scale! d 2 1) ;; здесь d стал кругом
(display-radius d)
(scale! d 2 1) ;; здесь d больше не круг
(display-radius d) ;; здесь будет ошибка "метод не найден"

monk ★★★★★
()
Ответ на: комментарий от monk

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

Так оно и должно быть. В свободном (он же — контекстозависимый) языке любое значение должно трактоваться от контекста. Иначе это крен в сторону B&D и приходим обратно к жабе, в конечном итоге.

J-yes-sir
()
Ответ на: комментарий от monk

что с точки зрения классо-ориентированных языков нонсенс: метод не может изменить тип объекта

Почему нонсенс? Меня как раз надавно опечалило, что в racket/class объекты не умеют менять свой класс.

qweqwe
()
Ответ на: комментарий от J-yes-sir

В свободном (он же — контекстозависимый) языке любое значение должно трактоваться от контекста. Иначе это крен в сторону B&D

То есть авторы PHP правы со своим:

123 == "123foo"
"1e3" == "1000"
"123" < "0124"
?

monk ★★★★★
()
Ответ на: комментарий от monk

Если круг/эллипс не могут изменяться методами, то круг является подтипом эллипса.

Вот я и говорю — если унаследовать круг от эллипса с не const методом scale, то можно испортить круг (Auth(Ellipse) сужается до Hom(Circle, Ellipse), а не Auth(Circle)). Но точно так же унаследовав чётные от целых чисел с не const методом inc мы сможем испортить чётные.

в этом смысле эллипс является потомком круга

Потомком это как? Подтипом не является.

motto
()
Ответ на: комментарий от qweqwe

Почему нонсенс?

Гм. Предлагаешь scale в CL написать как

(defmethod scale :after ((r circle) (p double) (q double))
  (when (/= (axis1 r) (axis2 r)) (change-class 'ellipse)))
?

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от motto

унаследовав чётные от целых чисел с не const методом inc

????

Что должен сделать 3.inc ? Изменить константу так, чтобы 2 + 2 == 3 и 3 == 4 ?

monk ★★★★★
()
Ответ на: комментарий от monk

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

if(fu()) doStuff
не приводя к булям. В нетривиальных случаях это может сэкономить кучу времени и нервов. Понимание языка предполагает и понимание контекстов языка.

J-yes-sir
()
Ответ на: комментарий от monk

В Scheme отдельный тип для AST (syntax), метапрограммирование там возможно, а всё усложнение метапрограммирования связано с гигиеной.

Метапрограммирование возможно в любом языке, который дает доступ к AST. Такие языки как Boo или Io дают доступ. Конкретная природа AST не имеет значения. Но я писал про Lisp, который не выделяет отдельного типа, что я считаю правильным. Вообще я против макросов и, следовательно, гигиены. ИМХО более правильный подход к созданию новых специальных операторов — fexpr'ы и реификаторы.

Эту статью Питмана я не читал. Чуть позже ознакомлюсь и может быть тогда смогу ответить аргументированно. Сейчас могу заметить только, что ты (вы?) как-то искаженно трактуешь мой пост.

должны быть только строки

Как раз таки от идеи, что программы — строки я хочу полностью абстрагироваться.

komputikisto
() автор топика
Ответ на: комментарий от motto

Auth(Ellipse) сужается до Hom(Circle, Ellipse), а не Auth(Circle)

Масштабирования из Auth(Ellipse) сужаются до масштабирований из Iso(Circle, Ellipse), а не до масштабирований из Auth(Circle).

motto
()
Ответ на: комментарий от monk

Что должен сделать 3.inc ? Изменить константу так, чтобы 2 + 2 == 3 и 3 == 4 ?

А почему обязательно менять? Пусть просто вернет инкремент. [

3.inc.inc
->>5
3
->>3

J-yes-sir
()
Ответ на: комментарий от monk

Ну круг с эллипсом из другой плоскости. Я про более простой случай. Например, сменить у объекта класс мальчика на девочку при вызове метода сменить пол.

qweqwe
()
Ответ на: комментарий от J-yes-sir

Да, правы. Просто на подобных языках хорошо писать никто не может. Если разработчик хорошо понял приведение типов

Ну да. Надо мне отсортировать массив строк. Стандартная реализация на PHP будет считать, что строка «123» меньше чем «0124». Что делать? Писать вручную посимвольное сравнение?

Ошибки, конечно можно избежать «если разработчик хорошо понял приведение типов», но излишняя самодеятельность языка больше вредит, чем помогает.

В твоём примере с if приведения типов нет. if считает «истиной» любое число не равное 0 или любой указатель не равный NULL.

monk ★★★★★
()
Ответ на: комментарий от J-yes-sir

А почему обязательно менять

Потому что «с не const методом inc». Вот я и попытался представить, что же он должен делать.

Иначе от того, что на число 4, которое имеет тип «чётное число» применится метод inc, число 4 не изменится, а метод просто вернёт число 5.

monk ★★★★★
()
Ответ на: комментарий от qweqwe

Например, сменить у объекта класс мальчика на девочку при вызове метода сменить пол.

Если у объекта есть метод «сменить пол», то это объект «человек», а не «мальчик». А если это функция «сменить пол», то просто меняй значение объекта на другое и все. Типа

(define (change-sex! person)
  (define old-sex (get-field (unbox person) sex))
  (set-box! person
    (make-object (if (eq? sex 'male) female% male%) (unbox person))))

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

не const методом inc

константу

?

inc ≡ x ↦ x + 1 : Iso(ℤ) ⊂ Endo(ℤ) ≅ State(ℤ, ⊤), inj ≡ x ↦ x : Mono(2ℤ, ℤ) ⊂ Mor(2ℤ, ℤ), 2ℤ < ℤ, inc ∘ inj : Iso(2ℤ, 2ℤ+1) ⊂ Mono(2ℤ, ℤ) ⊂ Mor(2ℤ, ℤ), Endo(ℤ) < Mor(2ℤ, ℤ) (http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)...). Эллипсы как ℤ и круги как 2ℤ так же.

motto
()
Ответ на: комментарий от motto

Здесь ты написал, что inc — функция с одним аргументом, а не метод. А выше предлагал «не const методом inc», что значит, что объект у которого вызвали этот метод должен изменять состояние. Вот я и пытаюсь добиться, какое состояние можно изменить, например, у числа 3 этим методом.

monk ★★★★★
()
Ответ на: комментарий от qweqwe

Просто постоянно ссылаться на этот объект через unbox чреват бойлерплейтом

вариант проще:

(define-syntax-rule (change-sex! person)
  (set! person (change-sex person)))

monk ★★★★★
()
Ответ на: комментарий от monk

Одно поле переопределить дело нехитроею А слабо вот так вот, с сохранением всех цепочек наследования



Man=function(){} 
Man.prototype.legs=2
Man.prototype.hands=2
Man.prototype.head=1
Man.prototype.fullName=function(){return this.name+" "+this.lastName}

Girl=function(name, lastname){
this.name=name
this.lastName=lastname
}
Girl.prototype=Object.create(Man.prototype)
Girl.prototype.power="week"
Girl.prototype.sex="female"

Boy=function(name, lastname){
this.name=name
this.lastName=lastname
}
Boy.prototype=Man.prototype
Boy.prototype.power="strong"
Boy.prototype.sex="male"

boy=new Boy("Jack", "Smith")

console.log(
boy.hands,
boy.legs,
boy.head,
boy.fullName(),
boy.power,
boy.sex
)

boy.name="Jane"
boy.__proto__=Girl.prototype

console.log(
boy.hands,
boy.legs,
boy.head,
boy.fullName(),
boy.power,
boy.sex
)


//  2 2 1 'Jack Smith' 'strong' 'male'
//  2 2 1 'Jane Smith' 'week' 'female'
Классы девочка и мальчик наследуются от класса человек. Мальчик — экземпляр класса мальчик. Превращаем его в девку полностью. Надеюсь понятно.

J-yes-sir
()
Ответ на: комментарий от qweqwe

ну и, например, объектная иерархия уже построена, контракты навешаны, и мне понадобилась эта функция со сменой класса. Что делать? Перелопачивать весь код и навешивать эти боксы или изначально боксить все объекты в ожидании возможной необходимости этой функции? Оба варианта так себе в плане абстракции.

qweqwe
()
Ответ на: комментарий от J-yes-sir

не приводя к булям

if ([]) {}
if ({}) {}

Сраный js, такой сраный.

anonymous
()
Ответ на: комментарий от J-yes-sir

да в js классы, видимо, открыты. С этим там все ок

qweqwe
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.