LINUX.ORG.RU

Преимущества лиспа на наглядных примерах


0

0

В литературе по лиспу довольно часто (обычно в предисловии) расписывают как крут лисп - позволяет "создавать язык программирования под конкретную задачу", существенно облегчает доработку имеющегося кода и т. д. А нет ли где-нибудь набора конкретных примеров, иллюстрирующих все эти преимущества? В стиле "вот задача, на c/python/что-то ещё она решается вот так, а на лиспе вот так"?

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

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

> т.к. всё же питон после лиспа - это не гламурно.

Да ладно, сделаешь себе eval-when и квазицитирование и почувствуешь себя как дома :-)

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

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

>> зачем мне что-то доказывать что-то непонятно кому?

> Обычно -- чтобы проверить, прав ли ты в своих убеждениях. А еще помогает свежий взгляд.

ээээ - непонятно зачем кому-то доказывать, что я прав? Я вполне успешно решаю свои задачи, используя соответствующие инструменты, и когда мне удобен Лисп, я беру его, когда Хаскель - его, когда нужно писать на С или С++, пишу на нем...

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

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

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

Ну вот, сам & потерял, а меня опять обидеть норовишь. Это у тебя в голове мутно, и ты написал какую-то фигню. Если пришёл список, содержащий N типов, не имеющих предков, то разбор их твоим способом займёт время O(N), а это подрывает позиции С++ как быстрого и масштабируемого языка. typecase - это, конечно, конструкция необходимая для полиморфизма. Его можно реализовать как O(log(N)). Значит, в правильном языке он должен работать как O(log(N)). Я не знаю, работает ли он так в реализациях лиспа, но ничто в стандарте лиспа этому не препятствует. Как минимум, в лиспе возможно построить хеш-таблицу, в которой тип является ключем (хотя, если честно, хеш-таблицы в лиспе - это отдельная и довольно грустная тема, т.к. адрес объекта не является константой). Можно ли так сделать в С++? Я не знаю. Для этого нужна некая константа, связанная с типом. В идеале - указатель на type_info этого типа, приведённый к целому, т.е., естественный хеш-код. Реально это целое - константа, но я не уверен, что компилятор это поймёт. Если поймёт, то по этому числу можно было бы сделать switch. Который бы превратился в безусловный переход по вычисляемому адресу. И мы получили бы O(log(N)). Можно попытаться сделать и O(1), но для этого нужно делать на каждое вхождение typecase в код новый виртуальный метод у Something и перекрывать его у Box-ов, а это - наверняка тоже морока. И код typecase при этом будет разбит на отдельные методы (что тоже стоит отдельных денег).

В общем, ты прав, я не знаю (не помню) плюсов. Я не помнил про type_info/typeid. И да, разбоксинг сделать можно, хотя не уверен, что его можно сделать быстрым. В общем, это неважно (мне), т.к. плюсы - это не язык моей мечты и я надеюсь, что мне не придётся на нём что-то писать без возможности просунуть метаслой на этапе компиляции.

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

> Если пришёл список, содержащий N типов, не имеющих предков, то разбор их твоим способом займёт время O(N), а это подрывает позиции С++ как быстрого и масштабируемого языка.

разбор их *твоим придуманным* (а не моим) способом займёт время O(N)

разбор *моим* способом -- это например virtual void print_value_size()

она работает за O(1) от N, где N -- количество типов на этапе компиляции.

(справедливости ради надо отметить, что твой способ можно тоже дооптимизировать до О(1))

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

> ээээ - непонятно зачем кому-то доказывать, что я прав?

Я сказал "проверить" -- это значит проверить для себя, а не доказать кому-то. Кто-то м.б. достаточно тупой и упрямый, чтобы ему это пошло не в прок.

З.Ы. Читаю девелопмент ради ошибок ньюбов. Иногда наводит на интересные мысли.

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

> В идеале - указатель на type_info этого типа, приведённый к целому, т.е., естественный хеш-код.

Поздравляю, ты изобрел vtbl.

Все, больше тебя в С++ не образовываю. Не в коня корм.

Преимущества лиспа в твоем исполнении продолжу читать и комментировать.

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

Предлагал Джоэл Спольски. Только предложение было -- написать 3D редактор для слабовидящих на Cobol'e.

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

>т.к. адрес объекта не является константой
И как это мешает? И что, вообще, значит "адрес" объекта? В лиспе отродясь ручного управления памятью не было.
Ссылка же на один и тот же объект всегда всегда дает T на EQ. SXHASH - аналогично.

guest-3484-2009
()
Ответ на: комментарий от imhotep

> Без обид - ты тпичный пример "буриданова осла", впрочем как и все фанатики лиспа.

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

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

Кстати о школьниках

>Да ладно тебе, это ж везде так. На прошлой работе связался с C++ и

Линуксом - шарился везде, начиная с ядра, через glibc, libxml2 и libalsa, к проклятому бусту. Всё глючит, всё патчил. И g++ сегфолтился на валидных конструкциях. И чипы, выпускающиеся уже 10 лет, виснут на определённых комбинациях, не смотря на сто ревизий. И китайское железо ещё, бывало, как завернёт, так потом два дня доходишь до светлой мысли, что, оказывается, за стеной, в другом подъезде, электрощиток стоит, который баги и вызывает. Нет щастья...

Типичный пример ШКОЛЬНЫХ рассуждений, мальчик не удосужился ознакомиться с банальной документацией на предмет EMC совместимости устройства и списывает на китайское производство. Школота - идите в школу.

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

Ты меня не перестаешь смешить - он минимум ОБЯЗАН заботиться о работоспособности своего поделия. Ты не в microsoft работаешь ? Я тебя сразу предупредить хочу - гламурышей из корпораций я так обламывал что они темы из платной техподдержки переносили в раздел личной переписки.

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

Интернет многих делает храбрыми. А вот штанишки-то мокнут, если фейс ту фейс встреча происходит...

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

>а про DDD читать пробовал?

Попробовал. В частнсти Эванса. С одной стороны интересно. С другой слишком низкоуровнево и не сдвигает дело с мертвой точки. Плюс трескучие выражения. Много чего из предложенного читал у Коплена, у него как-то проще и без выпендриваний.

ИМХО сущность предметной области (domain object) должна генерироваться на основании какого-то описания. Просто по-определению, т.к. не является задачей программиста. А вот реализацию механизмов описания будь добр...

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

За тобой дувдеван приедет если что, хотя такие школьники не нужны никому - существуй в своей кибуце в чехословакии :)

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

>Много чего из предложенного читал у Коплена, у него как-то проще и без выпендриваний

подход Коплиена - это FAST с фиксированной областью решений. в этом существенная разница. в DDD область решений подгоняется под предметную область

>ИМХО сущность предметной области (domain object) должна генерироваться

сущность предметной области тебе, в общем-то, дана

jtootf ★★★★★
()

Блин... за**али, чесслово.

Какая кому нах разница школьник или 35 летний дядя? Правильно, школьникам это и важно. Баттхерт.

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

>Соответственно, зачастую это просто говнософт на подобие чистилок реестра или торрентокачалок, который лично я у себя не использую,...

торрентокачалку таки можно один раз попользовать - если бумажный PAIP слишком дорог для шкубента

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

> Все, больше тебя в С++ не образовываю.
Ну твоя политика ясна, я просто надеялся на лучшее :) Ты легко задаёшь вопросы и ждёшь, что другие на тебя будут работать. А когда у тебя что-то спрашивают - хрен дождёшься ответа. "Мне это неинтересно". В общем, не удивляйся, если твои вопросы в мой адрес будут оставаться неотвеченными :)

den73 ★★★★★
()
Ответ на: комментарий от guest-3484-2009

>>т.к. адрес объекта не является константой
>И как это мешает? И что, вообще, значит "адрес" объекта? В лиспе >отродясь ручного управления памятью не было.

>Ссылка же на один и тот же объект всегда всегда дает T на EQ. SXHASH - >аналогично.

sxhash _не_ даёт одинаковые значения для eq объекта, если его содержимое меняется. Например, для случая cons-ов. Поэтому, нельзя построить eq-хеш таблицу на базе только одной sxhash. В чём тут ты видишь аналогию?

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

http://groups.google.com/group/comp.lang.lisp/browse_frm/thread/3449948ab0983...

Например, в SBCL хеш-функцией является адрес объекта (который может меняться при сборке мусора). Если какой-то объект подвинулся при сборке мусора (что необязательно, т.к. сборка поколенческая), то все ключи перевычисляются (или по всей таблице, или по странице - не помню). Т.е., сам код обращения к хеш-таблице достаточно сложен (читай - достаточно медленен). Плюс к тому, нужны какие-то блокировки во время обращения к хеш-таблице (не помню, почему, см. исходники SBCL).

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

> Ты легко задаёшь вопросы и ждёшь, что другие на тебя будут работать. А когда у тебя что-то спрашивают - хрен дождёшься ответа.

Ты не спрашивал, ты утверждал, напомню:

> Я не такой уж эксперт в плюсах, но я утверждал, что сделать это в С++ нельзя без написания хотя бы одной строчки кода для каждого типа, который не может получить соответствующий функционал через наследование. И продолжаю утверждать, покуда не буду ткнут носом в обратное.

Если бы ты *спросил* "а как достигается бла-бла? без кода, чисто идея, на пальцах?" я бы ответил про vtbl.

А вот писать тебе код, потом ты его нихрена не понимаешь, потом *утверждаешь*, потом требуешь дописать, ибо ты обиделся -- это как минимум утомительно. Это я делать не буду.

А на пальцах я продолжу объяснять.

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

И еще -- я еще понимаю, когда какой-то школьник брякнет что-нить про плюсы, а после ответа "это не так" замолкает. А вот от тебя явно не ожидал такого упорства в *утверждениях* насчет плюсов, при том, что тебе даже vtbl не известен.

Надеюсь, в будущем такое не будет повторяться.

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

> В общем, не удивляйся, если твои вопросы в мой адрес будут оставаться неотвеченными :)

Вполне возможно тебе действительно лучше статью написать.

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

> Если бы ты *спросил* "а как достигается бла-бла? без кода, чисто идея, на пальцах?" я бы ответил про vtbl.

Я попросил именно "ткнуть носом". Ты мог бы для начала попробовать рассказать идею. Я, кстати, погуглил и пришёл к выводу, что vtbl - это что-то эфемерное, т.е. не стандартизованное и непереносимое. Может быть, я в этом и неправ, мне особо некогда разбираться. И ещё вопрос, является ли указатель на vtbl, преобразованный к int, константой, по которой можно сделать switch. Мне кажется, это совершенно не факт, т.к. адрес будет известен только в момент линковки, а коды нужны уже во время компиляции.

Что конкретно я не понял в твоём коде? По-моему, всё понял, проблема в другом. Сравнение AS(int) с нулём - это и есть реализация typecase за O(N), если у нас не 1 int, а больше типов. Кстати, dynamic_cast<> - это не O(1) при глубоком наследовании.

Я так понял, ты предлагаешь организовать код по-другому. Вместо каждого места, где нужен typecase, плодить виртуальные методы в something, или делать визитор, или что-то иное. Этот подход, да, даёт O(1) (правда, не в случае визитора), но он стоит того, что тело typecase будет представлено N методами для разных типов. Разрушается лексическая область видимости и вообще всё становится более громоздким. Т.е., это будет не то же самое, что typecase в лиспе, а менее выразительная конструкция.

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

>sxhash _не_ даёт одинаковые значения для eq объекта, если его содержимое меняется. Например, для случая cons-ов.
Действительно, по equal смотрит. Фигню сморозил

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

Хэш-табличка по identity это (make-hash-table :test #'eq)
- сравнивает по ссылкам, и работает очень быстро.

guest-3484-2009
()
Ответ на: комментарий от guest-3484-2009

> Но ты так и не ответил, нахрена тебе в языке со сборкой мусора адрес
Адрес - это наиболее естественный ключ в eq хеш-таблице (и, как я уже отметил, он используется в этих целях в SBCL). Поскольку адрес в SBCL меняется, получается, что хеш-таблицы должны иногда перехешироваться. Это их удорожает.

> Хэш-табличка по identity это (make-hash-table :test #'eq)

> - сравнивает по ссылкам, и работает очень быстро.

Ссылка и проявляется как адрес (между сборками мусора). Работает не очень быстро. Посмотри на ход gethash в SBCL и увидишь, что там много всего. Если объекты неподвижны, то можно написать гораздо более быстрый код. Я не утверждаю, что неподвижные объекты "лучше" подвижных или что плюсы лучше лиспа. Я только утверждаю, что у хеш-таблиц при копирующей сборке мусора есть проблемы в достижении качества связки производительности-компактность-масштабируемость.

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

>реквистирую качественный срач на тему лиспа

присоединяюсь. но только качественный, с примерами, вроде "фразы о лиспе"

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

>Адрес - это наиболее естественный ключ в eq хеш-таблице (и, как я уже отметил, он используется в этих целях в SBCL)
Да какая разница, что там внутри SBCL. Зачем это надо пользователю лисп-системы?
>Если объекты неподвижны, то можно написать гораздо более быстрый код

А еще можно пользоваться массивом с числами. С байтами. Разделямым с сишкой. И работать с ним сишными функциями. И вообще, писать на сишке.
И грабить корованы, непременно.
>у хеш-таблиц при копирующей сборке мусора есть проблемы в достижении качества связки производительности-компактность-масштабируемость.

Вот сколько пользуюсь, а проблем с ними не испытывал.

guest-3484-2009
()
Ответ на: комментарий от guest-3484-2009

> Да какая разница, что там внутри SBCL. Зачем это надо пользователю лисп-системы?
Я просто тебе советовал посмотреть, если ты чего-то недопонимаешь в том, из чего состоит код работы с хеш-таблицами, и насколько он быстр.

Шутаут показывает, что лисп медленее не только С++/C, но и серверной Явы. Мне, например, как пользователю лисп-системы, было интересно узнать, почему это так и насколько ситуацию можно улучшить. Потому что и я тоже читал брехню о том, что лисп якобы быстрее С++. И даже пытался верить в неё. Теперь я уверенно говорю: брехня. Тем не менее, лисп быстрее всяких питонов и PHP на порядок-два и его скорость вполне достаточна для множества разных интересных приложений.

Другим пользователям лисп-системы это может быть ни за чем. Хотя и им тоже должно быть полезно знать, во что можно упереться при попытке сделать программу на лиспе быстрой.

А так - я ничего не доказывал и не утверждал, кроме того, что в лиспе eq-хеш-таблицы медленнее, чем хеш-таблицы в С/C++, основанные на адресах. И что сделать такие же быстрые/компактные/универсальные в лиспе не получится - мешает сборка мусора. Вот и всё. Причём медленее не в константное число раз, а есть ещё некие коэффициенты, зависящие от размера хеш-таблицы и/или интенсивности её модификаций и/или общего темпа порождения мусора.

А "не испытывал проблем" - это уже другая тема.

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

Я бы на твоем месте офигевал, как лисп может быть настолько быстрым.

И уверяю, скорость там местами страдает далеко не из-за хэш-табличек, и не из-за каких-то деталей реализации сборщика мусора(тем более, что на x86, за которым ты, скорее всего, сейчас сидишь, тот же SBCL ипользует GENCGC, отличающийся внушительной производительностью).
А из-за value typing, позднего связывания, и вообще, общей динамичности языка.

C++ и сишку физически он обогнать не может, на примитивных операциях, потому как те - это ассемблеры, фактически.
Но, программы на CL могут быть быстрее программ на C++, в силу того, что относительно большие программы на таких низкоуровневых языках, как сишка и плюсы, обрастают неизмеримым количеством костылей, сводящих на нет скорость базового языка, а в CL есть такие штуки defmacro и define-compiler-macro.

>я ничего не доказывал и не утверждал, кроме того, что в лиспе eq-хеш-таблицы медленнее, чем хеш-таблицы в С/C++, основанные на адресах

Но, по моим измерениям(в sbcl), быстрее аналогов в жабе и дотнете

guest-3484-2009
()
Ответ на: комментарий от guest-3484-2009

А по поводу шутаута.
Ну, он необъективен.
Вот те же pidigits.
http://shootout.alioth.debian.org/u32q/benchmark.php?test=pidigits&lang=all&box=1

Теперь скомпилируй такой код, и сравни.
(предполагается, что у тебя есть cffi, а в системе стоит gmp)

(eval-when (:compile-toplevel :load-toplevel :execute) 
  (require :asdf))
(eval-when (:compile-toplevel :load-toplevel :execute) 
  (asdf:oos 'asdf:load-op :cffi))
(eval-when (:compile-toplevel :load-toplevel :execute)
  (use-package :cffi))

(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (define-foreign-library gmp (t "libgmp-3"))
  (use-foreign-library gmp))

(defcstruct mpz-t 
	(mp-alloc :int)
	(mp-size :int)
	(mp-d :pointer))

(defcfun ("__gmpz_init" mpz-init :library gmp) 
	 :void
	 (int mpz-t))

(defcfun ("__gmpz_clear" mpz-clear :library gmp)
	 :void
	 (int mpz-t))

(defcfun ("__gmpz_init_set_ui" mpz-init-set-ui :library gmp)
	 :void
	 (rop mpz-t) (op :ulong))

(defcfun ("__gmpz_add" mpz-add :library gmp) 
	 :void
	 (rop mpz-t) (op1 mpz-t) (op2 mpz-t))

(defcfun ("__gmpz_mul_ui" mpz-mul-ui :library gmp) 
	 :void
	 (rop mpz-t) (op1 mpz-t) (op2 :ulong))

(defcfun ("__gmpz_addmul_ui" mpz-addmul-ui :library gmp) 
	 :void
	 (rop mpz-t) (op1 mpz-t) (op2 :ulong))

(defcfun ("__gmpz_submul_ui" mpz-submul-ui :library gmp) 
	 :void
	 (rop mpz-t) (op1 mpz-t) (op2 :ulong))

(defcfun ("__gmpz_tdiv_q" mpz-tdiv-q :library gmp)
	 :void
	 (q mpz-t) (n mpz-t) (d mpz-t))

(defcfun ("__gmpz_cmp" mpz-cmp :library gmp)
	 :int
	 (op1 mpz-t) (op2 mpz-t))

(defcfun ("__gmpz_get_ui" mpz-get-ui :library gmp)
	 :ulong
	 (op mpz-t))

(defparameter numer (null-pointer))
(defparameter accum (null-pointer))
(defparameter denom (null-pointer))
(defparameter tmp   (null-pointer))

(declaim (type foreign-pointer numer accum denom tmp))

(defun extract-digit (n)
  (mpz-mul-ui tmp numer n)
  (mpz-add tmp tmp accum)
  (mpz-tdiv-q tmp tmp denom)
  (mpz-get-ui tmp))

(defun next-term (k) 
  (let ((y2 (1+ (ash k 1))))
    (mpz-addmul-ui accum numer 2)
    (mpz-mul-ui accum accum y2)
    (mpz-mul-ui numer numer k)
    (mpz-mul-ui denom denom y2)
    nil))

(defun eliminate-digit (d)
  (mpz-submul-ui accum denom d)
  (mpz-mul-ui accum accum 10)
  (mpz-mul-ui numer numer 10)
  nil)

(defun pidigits (n)
  (let ((d 0) (i 0) (k 0) (m 0))
    (mpz-init tmp)
    (mpz-init-set-ui numer 1)
    (mpz-init-set-ui accum 0)
    (mpz-init-set-ui denom 1)
    (loop 
      (loop do
	(incf k)
	(next-term k)
	while (or (> (mpz-cmp numer accum) 0)
		  (/= (setf d (extract-digit 3)) 
		      (extract-digit 4))))
      (princ d) 
      (incf i)
      (setf m (mod i 10))
      (when (= m 0)
	#1=(write-string #.(format nil "~a:" #\tab))
	(princ i) #2=(terpri))
      (when (>= i n) 
	(return))
      (eliminate-digit d))
    (when (/= m 0)
      (setf m (- 10 m))
      (loop while (> m 0) do
	    (decf m) (write-char #\space))
      #1# (princ n) #2#)))

(defun main (&optional (n 27)) 
  (time 
    (with-foreign-pointer (numer #.(foreign-type-size 'mpz-t))
      (with-foreign-pointer (accum #.(foreign-type-size 'mpz-t))
        (with-foreign-pointer (denom #.(foreign-type-size 'mpz-t))
	  (with-foreign-pointer (tmp #.(foreign-type-size 'mpz-t))
	    (pidigits n)
	    (mpz-clear numer)
	    (mpz-clear accum)
	    (mpz-clear denom)
	    (mpz-clear tmp)))))))

guest-3484-2009
()
Ответ на: комментарий от guest-3484-2009

А зачем это _мне_ ? Запости свой код в шутаут. Там есть правила. Они сами ничего не пишут, только принимают/не принимают код от других авторов. Если твой код удовлетворяет правилам, то они его поместят и лисп может подвинуться повыше.

den73 ★★★★★
()
Ответ на: комментарий от guest-3484-2009

> Но, программы на CL могут быть быстрее программ на C++, в силу того, > что относительно большие программы на таких низкоуровневых языках,
> как сишка и плюсы, обрастают неизмеримым количеством костылей,

> сводящих на нет скорость базового языка, а в CL есть такие штуки

> defmacro и define-compiler-macro.

Я не спорю,что это возможно. Но реализована ли эта возможность? Для меня это требует доказательства. Единственный существенно сложный код, который я нашёл в шутауте - это регекспы. И почему-то возможность вызова compile в рантайме не помогла лиспу обогнать C и жабу, хотя вроде бы как раз С++ может только интерпретировать регэкспы, а SBCL может породить нативный код для конкретного регекспа. Известно, что, к примеру, в STL есть компромиссы с быстродействием, но я не знаю, достаточно ли их, чтобы лисп всё же обогнал С++ c STL.

И, опять же, я вполне одобряю скорость лиспа. Меня не столько удивляет скорость лиспа, сколько поражает безграмотность разработчиков питона, руби и php, которые, имея перед глазами лисп, до сих пор не смогли сделать свои языки быстрыми. Я бы на их месте просто реализовал бы эти языки на платформе Common Lisp и всё было бы гораздо круче, чем есть.

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