stdray: (Default)
[personal profile] stdray
Сначала я хотел просто написать, что майский конкурс по ФП закончился, и желающие могут посмотреть, что мы там устроили. А если что-то из увиденного понравится, то и проголосовать.

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


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

Спасибо мне! Сыграл в КО! Однако с языками программирования часто не прокатывает. Осилил %langname% и все, хватит, отвалите. Мне труп страуса нашептал, что у меня в руках ИНСТРУМЕНТ ВЫСОКОГО УРОВНЯ, а потом, прищурившись, добавил: "Полный по Тьюрингу, даааа". Ну теперь вообще ахуеть. Я даже слушать вас не хочу: жаба тормозит, сишка хуже ассемблера, додиез не знаю, зачем придумали, когда у них жаба есть, objc - это просто яблочная илитка, на лиспе искуственный интелект так и не зделали, лямбда-исчисление? исчисление что? Шли бы вы отсюда со своими историями. А потом человеку изменяет сознание и он начинает находить свои любимые костыли даже там, где их никогда и не было. Паттерн ПРИСПОСОБЛЕНЕЦ во все поля.

Я вспоминаю, как начав программировать на C#, испытывал физическую необходимость ОСВОБОДИТЬ ПАМЯТЬ и руками устроить связанный список. Потом-то оно уже проще идет. Знакомые вещи определяются сразу, а новые расширяют кругозор. Интересно узнавать новый язык, задаваясь вопросом: "А как я вообще раньше без этого жил?" Как будто собирается некий пазл, образуя целостную картину, а не "Вот тебе походный ножик. Теперь иди - строй дом".

Казалось бы, причем здесь Nemerle? Просто это язык программирования, который, на мой взгляд, заслуживает внимания.

Nemerle - ЯП общего назначения. Я не знаю, что такое общее назначение. Я так мало знаю о программировании микроконтроллеров и о программировании для суперкомьютеров и еще куче областей , что у меня с парнями, работающими в тех сферах, разная специальность. Я понимаю, что хороший программист должен найти себя в любой области, но у нас _настолько_ разные приоритеты, что придется сильно ломать себя. Я пишу средней толстоты эторпрайз (базворды прилагаются), потому для меня важны:
- Надежность. Ведь ничего падать не должно, а если упадет - перезапуститься. Конечно же, все логировать-логировать и алертить. И желательно все это задублировать.
- Прозрачность. Ведь вон та функция "ОТПРАВИТЬ ДЕНЬГИ НА ПЕЧАТЬ", должна отправлять деньги на печать всегда, а не только в состоянии суперпозиции ста тысяч "Если".
- Читаемость. Ну мы же все знаем, что самодокументируемый код - это миф, не так ли? Правда, мы так же знаем, что документирование всего и вся с последующей актуализацией - тот еще кошмар. Потому лучше писать кратко и информативно.
- Тестируемость. Необходимо проверить исправность каждой детальки еще до момента, когда конструктор будет собран. Мы же не хотим ОТПРАВИТЬ ЯЧЕЙКУ НА БОЕВОЕ ДЕЖУРСТВО, ЧТОБЫ ОНА ТАМ ПРОСТО СИГНАЛ РИСОВАЛА ТАБУРЕТОЧКОЙ, так?
- Совместимость. Хаскель исключительно хорош для новых проектов? А если не новых? Вместо библиотек даете возможность их написать? ВЕРНИТЕ НА МЕСТО МОИ ЛЮБИМЫЕ ВЕЛИКИ! БЫЫЫСТРААА!

Nemerle - статически типизуеммый язык программирования под .NET (есть реализация для Mono, но она вроде сломана) со всеми вытекающими. Язык поддерживает ООП в самом распространенном варианте ее реализации с одиночным наследованием, знакомый по Java и C#. Есть:
- Шаблоны (generics)
- Интерфесы
- Методы расширения
- Частичные (partial) классы
- Модификаторы доступа (internal, protected и тд)
- Весь набор, привычный C# программисту. )

Я не вижу смысла, останавливаться на ООП, потому как все достаточно стандартно. Идем далее. Nemerle реализует концепции функционального программирования:
- Нормальные кортежи
- Алгебраические типы данных
- Иммутабельность из коробки
- Паттерн матчинг
- Функции высшего порядка
- Частичное применение
- Вывод типов
- Замыкания
- Перегрузка и определение операторов с возможностью задания приоритета и ассоциативности
- Итераторы
- List comprehensions (не знаю, как переводится)
- Монады (нет, ввод-вывод не в монаде)
- Computation expressions (опять не знаю устоявшегося перевода)
- Опционально двухмерный синтаксис (синтаксис на отступах) через #pragma indent
- Опционально может в ленивость
- Оптимизация хвостовых вызовов
- События и делегаты (я думаю, эти вещи в этом блоке смотрятся лучше)

Вообщем-то весь джентльменский набор, необходимый, чтобы назвать язык функциональным. Стоит отметить, что Nemerle разрешает перегрузку функций (в отличии, например, от F#) и вывод типов спокойно разруливает, какую именно следует вызвать. Мне нравится, но наверное хаскелисты негодуют. Правда, Nemerle требует, чтобы на высшем уровне, то есть в классах и модулях (модуль является обычными статическим классом, для методов которого по умолчанию выставлен модификатор public) необходимо явно аннотировать типы. Я не думаю, что кому-то станет плохо от подобного требования, поскольку даже в Haskell, где система типов способна сама все выводить, аннотации типов все равно приводятся, поскольку по типам можно судить о природе процесса, выполняемого функцией. Еще одной особенностью является возможность "открыть" статических класс, импортировав его содержимое в текущее пространство имен. Может мелочь, но меня всегда раздражали все эти Console.WriteLine.

То есть, на первый взгляд, Nemele ничего особенно не представляет. К тому же разработчики старались не сломать мозг своей потенциальной аудитории, потому сделали синтаксис языка по возможности идентичным C#. Разработчики Scala, как я понимаю, пошли тем же путем. А вот ML-подобный синтаксис с непривычки может очень сильно напугать. Проверено. Язык задумывался, как замена C#, то есть каким C# должен был бы быть. И вы знаете, я согласен. Этих возможностей вполне хватает, чтобы комфортно программировать. F# как раз на этой позиции и находится, то есть со вкусом и без изъебств.

Теперь переходим к Nemerle непосредственно, к тому, что делает язык в большой степени уникальным, - макросам. У меня с макросами всегда были проблемы, сначало звучало страшно, потом было непонятно, где и как и применять. Похоже, это не только моя проблема. Иногда, я думаю, что после изобретения LISP'а, значительная часть развития языков программирования является размышлением на тему "Нужны ли макросы? Какими они должны быть тогда?" LISP'еры хоть и не изобрели искустевеный интелект, но очень многое дали миру: gc, графические интерфейсы и конечно макросы. Мне кажется, Ритчи с Томпсоном догадывались, что всего не предусмотришь, потому запили макры. Сишка используется до сих пор, многие даже испытывают к ней теплые чувства. А более другие люди решили, что макры не нужны, а теперь гордятся тьюринг-полным ТЕМПЛЕЙТО-ДОБРОМ. В Скалочку вот добавляют макросы, а я не видел, чтобы кто-то писал, мол Мартин Одерский не особо рубит в языках программирования.

Так с макросами какие проблемы? Во-первых, сразу начинается нытье, мол развалите весь синтаксис со всей семантикой, превратив язык в непонятную ёбу. Я так же ныл (может еще заною, кто его знает). Но это возврат к разговору о возможности прострелить себе ногу. Меня подзаебали эти разговоры, я решил для себя, что прострелить ногу можно везде и всегда. Мутабельность - прострелил. Энергичность - прострелил. Ленивость - иди чини ноги. Иммутабельность - не прочитал книгу по иммутабельным структурам данных? пиздуй в больницу чинить ноги. И так всегда, не бывает серебрянной пули, как не бывает языков общего назначения, как нет искуственого интеллекта. Именно поэтому, программисты существуют, существуют как профессия, как специалисты, а не просто прослойка между креслом и монитором. Хотя надо отдавать себе отчет в собственных возможностях. Это вам не придти и перевести все критические бизнес-процессы на Хаскель из-за того, что кто-то научился Фибоначи считать. И я не один такой, создатель Лифта такой же точки зрения придерживается. То есть в любом случае некоторый опыт необходим.

Во-вторых, макросы сложные. В Nemerle не сложные, то есть при написании макросов используется тот же самый Nemerle и его возможности, никакого специального DSL не требуется. Держим в уме знание, что макрос - функция с типом List[PExpr] -> PExpr, выполняемая во время компиляции, и все будет хорошо. Макросы гигиеничны, то есть при совпадении имен переменной из макроса и контекста, в котором макрос разворачивается, имя из макроса будет автоматически переименовано, хотя это поведение можно отключать, тогда значение будет связано с ближайшим именем в контексте. Таким образом, кстати, работает макра regex match, которую я использовал в своем решении.

Макросы бывают разными
- Макросы выражения: можно сделать printf("%d%f", x, y), который будет на этапе компиляции проверять соответствие количества параметров и их типов строке форматирования; многие компоненты языка такие как if else, while, for и другие реализованы через такие макросы
- Макросы-операторы: собственно через них реализует переопределение и создание новых операторов. Можно задавать приоритет и ассоциативность. Например, с их помощь в Nemerle сделаны pipe-операторы |> и <|
- Мета-атрибуты - видимо, так называются из-за сходства в записили с атрибутами C#. Позволяют изменить класс, метод, свойство и так далее. Для меня самый впечатляющим примером использования является макрос PegGrammar, который по заданной PEG грамматике создает класс-парсер ее разбирающий. Мой игрушечный пример можно увидеть тут. Если игрушки вам не по душе, то можно глянуть на серьезное использование для разбора грамматики C#.
- Лексические макросы: к сожалению, я еще не успел с этим разобраться, но похоже, макра конструирования XML, напоминающая синтаксис для работы с XML в Scala, относится именно к этой категории.

Мне очень хочется, рассказать о макросах Nemerle, кратко и содержательно, чтобы вы могли испытать то же ВАУ, которое испытываю я. Но, по всей видимости, пока в моем мозгу все по полочкам не разложится, я не смогу внятно излагать. Главное, что я хочу донести, что макросы в Nemerle - это очень гибкое средство, которое красиво и лаконично ложится на язык. Синтаксис языка во много сделан на макросах, кодогенерация требующая в C# сторонных тулзовин отлично реализуется, многие хорошие фичи, которые приходят в C#, как новые элементы языка, делаются на макросах. Я думаю, так ожидаемые Type Providers из F# 3.0 делаются на макросах на ура. То есть я пока не вижу, где заканчиваются возможности, но четко представляю, как применять эти вещи в работе: будь-то автоматическая имплементация всяких IDataErrorInfo с INotifyChanged или как присахарить и обезопасить работу с каким-нибудь SqlCommand. При этом я без проблем, могу использовать существующий C# код, могу использовать в C# проектах код на Nemerle (пока не тестировал). Я считаю, что Nemerle - более удобный и качественный инструмент для решения мох задач.

Надесь, было хоть чуть-чуть интересно. Но если вы внезапно дочитали до сюда и считаете все вышенаписаное бредом идиота , хотя бы начинайте псить. Мне приятно будет)


Update: Как-то получилось, что обсуждение велось параллельно в двух местах: тут и еще одна ветка обсуждения.

(no subject)

Date: 2012-05-16 10:11 am (UTC)
From: [identity profile] thesz.livejournal.com
>Я говорю, что эта часть юзкейса - это не бОльшая часть макросов. Даже не значимая часть. И если ее убрать - то ничего не меняется.

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

Здесь я охотно соглашусь с моим неведением, если увижу настоящее исследование. В противном случае я бы предпочёл, чтобы вы больше не говорили о большей и меньшей частях, если вы... не запрещаете, конечно, но говорите о моей неправомочности делать выводы.

>Еще раз - оптимизация и задерживание аргументов это совершенно не значимая часть макросов. То есть из тысячи макросов этим занимается... ну может один или два.

Есть ли у вас исследования, как применяются макросы?

Да даже если и нет, то это ваше высказывание означает, что макросы используются лдя расширения синтаксиса. См. выше про синтаксический подход.

>Никакой реализации if без использования вшитого кейза причем так, чтобы if использовал стандартные True и False вы не предложили.

Я и не должен был. Это за меня сделали в 1936 году. Я всего лишь показал, что это возможно и указал более авторитетный источник.

Прочитайте же наконец литературу. Хотя бы Википедию про кодирование Чёрча. В "Programming in Martin-Loef Type Theory" рассказывается, как сделать всё то же самое, только со строгой типизацией и без парадоксов. "Всё то же самое" - определение типов, рекурсивных в том числе.

>Вы выдаете собственное мнение за принятый в CS взгляд.

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

(no subject)

Date: 2012-05-16 11:25 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Есть ли у вас исследования, как применяются макросы? Вы же наверняка интересовались этим вопросом больше, чем я. Проводили исследования, владеете цифрами, всё такое.

Стоп-стоп-стоп, зачем мне проводить исследования? Это же _вы_ ворвались, размахивая яки знаменем своими "90% случаев" (и не первый раз ворвались, надо заметить). Никаких исследований ни я, ни кто-либо еще, естественно, не проводил - просто потому что такие исследования принципиально невозможно провести так, чтобы получить хоть сколько-нибудь корректный результат. И что, этот факт позволяет врываться и нести ересь о 90%? Вы тогда так и говорите: не "90%", а "на мой взгляд человека, с макросами никогда не работавшего и об их применении ничего не знающего - 90%". Это ваше личное мнение, основанное на вашей личной практике. И это будет верно, и никто вам не сможет ничего возразить. А дальше можете ссылаться на Алана Кея и других альтернативно мыслящих, оставшихся в прошлом 50-летней давности с фекспрами, от которых все сообщество (за исключением тех самых альтернативных единиц) отказалось ввиду того, что эти самые фекспры на практике доказали свою говенность.

> В противном случае я бы предпочёл, чтобы вы больше не говорили о большей и меньшей частях

Конечно, не буду. Если вы не будете. Все честно, так? Ну а если вы сами бросаетесь бездоказательными высказываниями - до даете мне полное моральное право поступить аналогичным образом. Сим я заявляю, например, что статическая типизация может исправить не более сотой доли процента от общей совокупности ошибок - и, тем самым, совершенно не нужна. Дико же звучит? Дико. Вот ваши сентенции звучат столь же дико. Будем и дальше бросаться бредовыми бездоказательными заявлениями или переведем беседу в конструктивное русло? Мне без разницы, если честно. Мне и друг друга потроллить будет весело и серьезно те или иные аспекты ЯП обсудить. Оставляю выбор вам.

> Да даже если и нет, то это ваше высказывание означает, что макросы используются лдя расширения синтаксиса. См. выше про синтаксический подход.

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

И еще два вопроса - замена аппликативного порядка вычислений на нормальный или замена системы типов являются изменением семантики? Или это изменения синтаксиса?

> Я всего лишь показал

Что вы показали? Что в какой-то реализации хаскеля это можно? Конечно, можно - лямбда-исчисление является тьюринг-полной системой, по-этому любую программу можно свести к вычислительно эквивалентному лямбда-терму. Причем и в ленивом, и в энергичном случае. Это не отменяет того факта, что если у вас примитивные True/False, то case всегда будет спецформой.

> Хотя бы Википедию про кодирование Чёрча.

Да все знают про кодирование Черча, чего вы повторяете как попугай? Вам еще несколько постов назад сказали, как сделать if со _встроенными_ True/False. Если у вас вместо булевых значений лямбды - тут любой дурак справится. Причем порядок вычислений не важен - это одинаково просто и в нормальном и в аппликативном случае.

> О, моё мнение основывается на принятом в CS.

Правда? И где же можно узнать о теоремах где доказано, что нормальный порядок более мощен? Именно мощен.

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

Что значит "выразить большее количество программ"? Любая программа в нормальном порядке тривиальным синтаксическим преобразованием (описываемым в две строчки) приводится к вычислительно эквивалентной программе в аппликативном порядке. Обратное, кстати, тоже верно, правда преобразование не настолько тривиально. Как вы умудряетесь сравнивать "мощность" совершенно эквивалентных с вычислительной точки зрения систем - мне совершенно неясно. Может, все дело в понятии "мощность"? Что вы под ним подразумеваете?

(no subject)

Date: 2012-05-16 12:35 pm (UTC)
From: [identity profile] theiced.livejournal.com
проще было сказать этому говну "ты хуй". он всё равно тупой и не поймёт.

(no subject)

Date: 2012-05-16 08:01 pm (UTC)
From: [identity profile] thesz.livejournal.com
>Стоп-стоп-стоп, зачем мне проводить исследования? Это же _вы_ ворвались, размахивая яки знаменем своими "90% случаев" (и не первый раз ворвались, надо заметить).

Да. Мой анекдотический опыт говорит вот это. Я просто-напросто один раз посчитал макросы в доступных мне программах на языках высокого уровня, натурально. В основном, это был Си, но и тикль встречался. На ассемблере и Форте (в последнем макросы весьма условны) всё по-другому, но там и семантика такая, что не изменив её, далеко не уедешь.

Это было давно, 1999-2001 год.

Попробуйте провести такой опыт.

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

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

(ибо я тут уже приводил кусочки кода, имею право потребовать)

>Что вы показали? Что в какой-то реализации хаскеля это можно? Конечно, можно - лямбда-исчисление является тьюринг-полной системой, по-этому любую программу можно свести к вычислительно эквивалентному лямбда-терму. Причем и в ленивом, и в энергичном случае. Это не отменяет того факта, что если у вас примитивные True/False, то case всегда будет спецформой.

Да и наплевать. В случае расширение RebindableSyntax оператор if...then...else... заменяется на ifThenElse a b c, натурально. Я могу последовательно убирать case, например. В моём HHDL так и сделано, кстати. В язык описания аппаратуры введены алгебраические типы и сравнение с образцом.

>Правда? И где же можно узнать о теоремах где доказано, что нормальный порядок более мощен? Именно мощен.

http://en.wikipedia.org/wiki/Lambda_calculus

Applicative order is not a normalising strategy. The usual counterexample is as follows: define Ω = ωω where ω = λx.xx. This entire expression contains only one redex, namely the whole expression; its reduct is again Ω. Since this is the only available reduction, Ω has no normal form (under any evaluation strategy). Using applicative order, the expression KIΩ = (λx.λy.x) (λx.x)Ω is reduced by first reducing Ω to normal form (since it is the rightmost redex), but since Ω has no normal form, applicative order fails to find a normal form for KIΩ.

In contrast, normal order is so called because it always finds a normalising reduction, if one exists. In the above example, KIΩ reduces under normal order to I, a normal form. A drawback is that redexes in the arguments may be copied, resulting in duplicated computation (for example, (λx.xx) ((λx.x)y) reduces to ((λx.x)y) ((λx.x)y) using this strategy; now there are two redexes, so full evaluation needs two more steps, but if the argument had been reduced first, there would now be none).

Засим позвольте с вами распрощаться. Вы даже Википедию не освоили.

Натурально, отвечать не будут нигде.

(no subject)

Date: 2012-05-17 12:17 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Да. Мой анекдотический опыт говорит вот это. Я просто-напросто один раз посчитал макросы в доступных мне программах на языках высокого уровня, натурально. В основном, это был Си, но и тикль встречался. На ассемблере и Форте (в последнем макросы весьма условны) всё по-другому, но там и семантика такая, что не изменив её, далеко не уедешь.

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

> Приведите, пожалуйста, все макросы из какой-либо не очень большой программы на Лиспе.

Нет, не имеете. Я вам в прошлом посте задал конкретный вопрос исключительно после ответа на который вы и сможете что-то там "требовать". Вы на него так и не ответили.

> Да и наплевать. В случае расширение RebindableSyntax оператор if...then...else... заменяется на ifThenElse a b c, натурально.

Вы что, совсем слепой? Еще раз - любой идиот может убрать кейз. Задача - убрать кейз, сохранив оригинальные True/False. Вот эта задача не решается.

> Applicative order is not a normalising strategy. The usual counterexample is as follows: define Ω = ωω where ω = λx.xx. This entire expression contains only one redex, namely the whole expression; its reduct is again Ω. Since this is the only available reduction, Ω has no normal form (under any evaluation strategy). Using applicative order, the expression KIΩ = (λx.λy.x) (λx.x)Ω is reduced by first reducing Ω to normal form (since it is the rightmost redex), but since Ω has no normal form, applicative order fails to find a normal form for KIΩ.

>In contrast, normal order is so called because it always finds a normalising reduction, if one exists. In the above example, KIΩ reduces under normal order to I, a normal form. A drawback is that redexes in the arguments may be copied, resulting in duplicated computation (for example, (λx.xx) ((λx.x)y) reduces to ((λx.x)y) ((λx.x)y) using this strategy; now there are two redexes, so full evaluation needs two more steps, but if the argument had been reduced first, there would now be none).

Как видно, я был прав - принятое в CS мнение состоит исключительно в том, что энергичные и ленивые программы отличаются исключительно формально - тем фактом, что некоторые программы, эквивалентные при нормальном порядке могут быть неэквивалентными при аппликативном, что никак не сказывается на выразительной мощности. За сим вы признаетесь треплом обыкновенным, не способным ответить за слова.

(no subject)

Date: 2012-05-17 12:33 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Приведите, пожалуйста, все макросы из какой-либо не очень большой программы на Лиспе.

Хотя ладно, пойду вам навстречу (хотя вы результат все равно проигнорируете и кукарекнете что-нибудь на тему "кококо плохая семантика кококо поэтому без макросов никак кококо"). Программа, вычисляющая факториал:
[code]
#lang racket

(define (fact n)
(define (rec n acc)
(if (= n 0)
acc
(rec (sub1 n) (* acc n))))
(rec n 1))
[/code]
Во время экспанда 8 макровызовов. Самих макросов 2: define и printing-module-begin, но define весьма нетривиальный (считайте, что эквивалентен целой библиотеке, вообще, это весьма характерно для макросов, когда один единственный макрос заменяет целую библиотеку функций. Именно по-этому нету никакого корректного способа сравнения).

December 2019

S M T W T F S
1234567
891011121314
15161718192021
222324252627 28
293031    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags