Forth и другие саморасширяющиеся системы программирования Locations of visitors to this page
Текущее время: Чт янв 28, 2021 15:21

...
Google Search
Forth-FAQ Spy Grafic

Часовой пояс: UTC + 3 часа [ Летнее время ]




Начать новую тему Ответить на тему  [ Сообщений: 5 ] 
Автор Сообщение
 Заголовок сообщения: Статья What the Hell is Forth (Что, черт возьми, такое Форт?
СообщениеДобавлено: Вт дек 29, 2020 19:34 
Не в сети

Зарегистрирован: Пн янв 07, 2013 22:40
Сообщения: 1341
Благодарил (а): 3 раз.
Поблагодарили: 48 раз.
Интересная статья-эссэ о Форт (может быть и переведена для местного размещения :)

What the hell is Forth?

P.S. Google translete выдаёт примерно такой перевод
Код:
Что, черт возьми, такое Форт?  21 февраля 2019 г.,

Forth, пожалуй, самый маленький из возможных интерактивных языков программирования. Он крошечный по ряду размеров:

    Количество кода, необходимого для его реализации
    Размер генерируемого кода
    Объем используемой памяти
    Количество функций, которые он считает необходимыми для полезной работы

Это язык, который делает сложность болезненной, но он показывает, что удивительное количество может быть достигнуто, не вводя ничего. Форт - это противоположность «раздуванию». Если вы когда-нибудь думали: «Боже мой, это приложение для чата на основе Electron занимает 10% моего процессора в режиме ожидания, какого черта оно ДЕЛАЕТ, современные вычисления сошли с ума», Форт должен сказать вам, что вычисления сошли с ума десятилетия назад, и эти программы могли бы делать НАМНОГО БОЛЬШЕ с НАМНОГО МЕНЬШЕ.

    Какое значение "Форт"

О Форте есть выражение: «Если вы видели один Форт, вы видели один Форт». Forth не является строго определенным языком, хотя существует стандартизованный диалект; это скорее набор идей, которые хорошо работают вместе.

В прошлом месяце я написал крошечную систему Forth на 286-м компьютере под управлением MS-DOS с использованием Turbo C ++ 1.01. Я впервые использую Forth в гневе, хотя я много читал об этом 15 лет назад. Когда я говорю о моем Forth, я имею в виду систему, буквально собранную за две недели, написанную кем-то, кто действительно не так хорошо знает Forth. Он медленный, очень нестандартный и не очень полезен, но мне очень понравился процесс его написания. Если вы седой старый фортский грогнард, пожалуйста, дайте мне знать, если я что-то исказил.

    ЧТО ФОРТ НЕ ДЕЛАЕТ
   
Вот неполный список вещей, которые вы, как программист, можете принять как должное, что Forth в чистом виде обычно считает ненужными тратами:
 
    Вывоз мусора
    Распределение динамической памяти
    Мусор
    Безопасность памяти
    Статические типы
    Динамические типы
    Объекты
    Полиморфные методы
    Замыкания
    Лексическая область видимости
    Представление о том, что глобальные переменные в любом случае «плохие»
    Локальные переменные
    Возможность писать операторы «ЕСЛИ» в REPL

Большинство или все из них могут быть добавлены в язык - стандарт Forth, ANS Forth, определяет слова для динамического распределения памяти и локальных переменных. Есть много объектных систем, которые люди построили на основе Forth. Forth - гибкая среда, если вы готовы поработать.
   
Но изобретатель Форта, Чак Мур, буквально сказал в 1999 году : «Я по-прежнему твердо уверен, что локальные переменные не только бесполезны, но и вредны». В философии Forth необходимость использования локальных переменных является признаком того, что вы недостаточно упростили задачу; что вы должны перестроить вещи так, чтобы смысл был ясен и без них.

    Как это выгглядит

Основная часть Forth заключается в том, что все функции или «слова» в терминологии Forth работают с «стеком». Слова принимают аргументы из стека и возвращают свои результаты в стек. Есть несколько примитивных встроенных слов, которые не выполняют никакой полезной работы, кроме манипулирования стеком.
   
Это означает, что написание дерева выражений в виде кода Forth превращается в постфиксную нотацию.
(1 + 2) * (3 - 4) становится 1 2 + 3  4 - *  Запись числа в Forth означает «положить это число в стек».

Синтаксис Forth, за некоторыми исключениями, в корне поразительно прост: все, что не является пробелом, является словом. Как только интерпретатор нашел слово, он ищет его в глобальном словаре и, если в нем есть запись, выполняет ее. Если записи нет, интерпретатор пытается разобрать ее как число; если это сработает, он помещает это число в стек. Если это тоже не число, он выводит ошибку и продолжает работу.
   
Ой, я хотел описать синтаксис, но вместо этого я записал всю семантику интерпретатора, потому что она умещается в трех предложениях .

Исключением из правила «все, что не является пробелом, является словом», является то, что интерпретатор - не единственный фрагмент кода Forth, который может потреблять ввод. Например,  ( - это слово, которое считывает ввод и отбрасывает его, пока не найдет символ ). Вот как работают комментарии: интерпретатор видит символ ( с пробелом после него, запускает слово, а затем следующий символ, на который он смотрит, находится после окончания комментария. Вы можете тривиально определить ( в одной строке Forth.

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

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

Отчасти загадочность Forth заключается в том, что с ним можно получить очень метациркулярную форму - слова управления потоком, такие как IF и FOR, реализованы в Forth, а не в составе компилятора/интерпретатора. Таковы комментарии и строковые литералы. Сам компилятор/интерпретатор обычно так или иначе написан на Forth. Оказывается, вы можете отбросить практически все удобства современного программирования и все же получить полезный язык, который можно расширять в любом направлении, в котором вы решите приложить усилия.

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

Ментальная модель начальной загрузки системы Forth выглядит примерно так:
         
    Пишите примитивные слова на ассемблере - сюда входит полная «виртуальная машина» Forth, в отличие от интерпретатора/компилятора языка Forth. Набор встроенных слов может быть очень и очень маленьким - в документе «Обзор eForth», подготовленном Ч. Тингом , который, как я считаю, рекомендуется как отличное подробное описание того, как создать среду Forth, Тинг утверждает, что его система построена из 31 «примитивного» слова, написанного на ассемблере.
     
    Соберите вручную «байт-код виртуальной машины» для интерпретатора/компилятора и требуемых зависимостей - из-за чрезвычайной простоты виртуальной машины вы обычно можете запрограммировать свой макроассемблер для выполнения этой работы, и поэтому это может значимо напоминать действие простого написания кода Forth. прямо

    Напишите все новые слова, используя только что запущенный интерпретатор/компилятор

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

    ЧТО СТОИТ В ПИСАНИИ

    Любое слово, которое принимает более двух или трех параметров, является кошмаром для чтения или записи.

Прямо сейчас в моей кодовой базе есть слово, в котором используются две глобальные переменные, потому что я не могу справиться с манипуляциями со всеми значениями в стеке. Это слово абсолютно не повторяется, и в какой-то момент мне придется переписать его так, чтобы оно было, и я не с нетерпением жду этого . Если бы у меня были локальные переменные, это привело бы к значительно меньшей проблемe. Но есть также часть меня, которая думает, что должен быть какой-то способ переписать это, чтобы было проще, чего я еще не придумал.
   
В моей кодовой базе есть еще одно слово, которое принимает 4 или 5 параметров, которое мне удалось написать, разбив его, например, на 8 более мелких слов, в процессе написания/переписывания в течение часа или двух. Я очень гордился тем, что оно наконец-то заработало, но, честно говоря, я думаю, что было бы довольно тривиально писать на C с локальными переменными. Я скучаю по ним.

    Дерьмо вылетает

Помните ту часть о небезопасности памяти? Да, есть все виды способов, что в  своенравной системе Forth может пойти не так. Я забыл один раз DROP  в часто используемом слове, и мой компьютер жестко заблокировался при переполнении стека. (Честно говоря: мой компьютер был 286-м под управлением MS-DOS, так что я уже был в ситуации, когда его программирование означало его перезагрузку, когда я неизбежно что-то облажался.)

    Несуществующие сообщения об ошибках

Единственное сообщение об ошибке, которое появляется в моей системе Forth: если она не распознает слово «foo», она выводит «foo?» Если, например, я пишу IF оператор, но забываю закончить его THEN, я не получаю ошибки компиляции, я получаю - как вы уже догадались - жесткое падение во время выполнения.

    КАКИЕ ПРАВИЛА НАПИСАНИЯ В ФОРТ

    Он чертовски компактный
   
Большинство слов, которые я пишу, представляют собой буквально одну строку кода. Они делают небольшую работу и уходят.
 
  Это прямо, как ад

Построение абстракций в Forth ... отличается от построения абстракций на других языках. Это все еще действительно основная, важная вещь, но поскольку создание сложного/дорогостоящего кода - это очень большая работа, наложение дорогих абстракций друг на друга не очень разумно. Таким образом, Вы остаётесь с самыми простыми строительными блоками, чтобы выполнять свою работу как можно проще.

    Вы полностью уполномочены решать любые проблемы с вашим конкретным рабочим процессом и средой.

Люди превращают системы Forth в крошечные операционные системы с текстовыми редакторами, и я совершенно не понимал этого импульса, пока не написал свой собственный. Интерпретатор Forth - это интерактивная командная строка, и вы можете сделать ее своей собственной. Раньше я написал декомпилятор, потому что это было легко. Это как половина экрана кода. Бывают случаи, когда он падает, но я написал это примерно за полчаса, и он работает достаточно хорошо для того, что мне нужно.

    Все крошечные, их легко изменить или расширить

Помните, я сказал, что написал декомпилятор, потому что это было легко? Еще кое-что я изменил за один-два вечера:
         
    Добавлена ​​кооперативная многозадачность (зеленые темы)
    Пользовательские переопределения ввода-вывода, чтобы мои интерактивные сеансы REPL можно было сохранить на диск
    Переписал основной цикл интерпретатора на Forth
    Переписал цикл виртуальной машины, чтобы не использовать стек C
    Инструментирование виртуальной машины с помощью отладочных данных для обнаружения ошибки сбоя

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

Forth - это динамический язык, в котором единственным типом является «16-битное число», и вы можете делать с этим числом все, что хотите. Это, конечно, чертовски опасно, но если вы пишете код, у которого нет шансов обрабатывать произвольный враждебный ввод из Интернета (например, мой вышеупомянутый MS-DOS 286), удивительно, насколько это освежает и весело.

    ЭТО ЗВУЧИТ ИНТЕРЕСНО, КАК НАИЛУЧШИЙ СПОСОБ УЗНАТЬ БОЛЬШЕ

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

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

    ЧТО Я ИЗВЛЕКАЮ ИЗ ЭТОГО

Меня очень интересуют альтернативные взгляды на то, как могут выглядеть вычисления и для кого они могут быть.
В Forth заложено несколько очень интересных идей:

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

Я часто задаюсь вопросом, как может выглядеть более доступный Форт; есть ли более гибкие, составляемые, простые абстракции, такие как «слово» Форта?
Наши текущие парадигмы GUI не могут быть неуменьшаемы по сложности; есть ли радикально более простая альтернатива, которая расширяет возможности людей?
Как еще мог бы выглядеть язык программирования индивидуального масштаба, который не только обеспечивает простоту, но и полностью исключает сложность?
   
Forth - радикальный язык, потому что он не «масштабируется»; вы не можете построить в нем огромную систему, которую никто не понимает и не ожидает, что она будет работать. Большинство систем, которые я использовал, которые не масштабируются - Klik & Play, Hypercard, Scratch и тому подобное - созданы для обеспечения доступности.
Форт -  нет; он предназначен для  рычагов. Это интересное дизайнерское пространство, о котором я даже не подозревал.

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


Последний раз редактировалось KPG Чт дек 31, 2020 19:16, всего редактировалось 12 раз(а).

Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения: Re: Статья What the Hell is Forth
СообщениеДобавлено: Ср дек 30, 2020 01:20 
Не в сети
Administrator
Administrator
Аватара пользователя

Зарегистрирован: Вт май 02, 2006 22:48
Сообщения: 7123
Благодарил (а): 17 раз.
Поблагодарили: 119 раз.
Цитата:
это скорее набор идей, которые хорошо работают вместе.

Очень точное определение!

Цитата:
Вот неполный список вещей, которые вы, как программист, можете принять как должное, что Forth в чистом виде обычно считает ненужными тратами:

Стоп-стоп... выше было указано, что автор написал Форт самостоятельно? Так это относится к его реализации, к другим реализациям, к Форту вообще? Впрочем, тут скорее дело обстоит несколько иначе - перечислены в том числе вещи, которые вошли в обиход позже Форта. А некоторые из перечисленных - просто популярный подход, или же могут быть реализованы безотносительно языка. Например, никто не мешает добавить слово, которое будет динамически выделять память, вызывая соответствующую функцию OS API (ведь другие языки в основном так и делают). Ну да, не за что зацепиться из привычного, хочется сразу писать знакомые конструкции. Но идея-то не в том, чтобы в 1970-м году сразу сказать "это язык, который лет через 20 будет хорош в ООП и поддерживать сборку мусора". Автор языка все-таки Чарльз Мур, а не Марти Макфлай :)

Цитата:
Но изобретатель Форта, Чак Мур, буквально сказал в 1999 году : «Я по-прежнему твердо уверен, что локальные переменные не только бесполезны, но и вредны».

Сказал и сказал. А Ньютон считал, что свет - это поток корпускул. Мы же теперь не отменяем закон всемирного тяготения. Вариантов много - "Мур имел в виду определенные условия применения", "Мур ошибся", "Мур работал с другими задачами". Сегодня есть несколько вариантов реализации локальных переменных. Computer science развивается, и вполне можно обогащать Форт тем, что подходит.

Цитата:
Во многих смыслах это похоже на странную обратную шепелявость без скобок.


Эээ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator

Код:
const double = (n) => n * 2;
const increment = (n) => n + 1;

// without pipeline operator
double(increment(double(double(5)))); // 42

// with pipeline operator
5 |> double |> double |> increment |> double; // 42


Что, простите? :shuffle; ПОСТФИКС В JAVASCRIPT!!??? Ой, так оно же, оказывается, где-то даже удобно и хорошо смотрится :))

Цитата:
Любое слово, которое принимает более двух или трех параметров, является кошмаром для чтения или записи.

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

Цитата:
Помните ту часть о небезопасности памяти? Да, есть все виды способов своенравная система Forth может пойти не так. Я забыл один раз DROP  в часто используемом слове, и мой компьютер жестко заблокировался при переполнении стека. (Честно говоря: мой компьютер был 286-м под управлением MS-DOS, так что я уже был в ситуации, когда его программирование означало его перезагрузку, когда я неизбежно что-то облажался.)


Ничего странного, если вчитаться в текст. Да, если писать неаккуратно, оно упадет. Оно упадет во многих языках, и практически гарантированно на ассемблере. Вариантов избежать этого, кстати, масса, но все они заставляют идти на компромисс в смысле производительности или объема памяти. Забыть DROP в цикле - ну а что тут странного? Да, упадет. Тело цикла хорошо бы для начала отладить на одной итерации - от единственного лишнего DROP оно редко падает (часто в реализация предусматривают буфер на такой случай). Это опять-таки вопрос практики.

Цитата:
Единственное сообщение об ошибке, которое появляется в моей системе Forth: если она не распознает слово «foo», она выводит «foo?» Если, например, я пишу IF оператор, но забываю закончить его THEN, я не получаю ошибки компиляции, я получаю - как вы уже догадались - жесткое падение во время выполнения.


Ну нет, это уже претензия к частной реализации. Контролировать control flow stack тоже несложно. Если после ; осталось число (обычно пара чисел - id addr), элементарно по id управляющей структуры вывести сообщение "слово-id без закрывающего слова".

Цитата:
и я совершенно не понимал этого импульса, пока не написал свой собственный.

Именно! :)

Цитата:
Еще кое-что я изменил за один-два вечера:
         
    Добавлена ​​кооперативная многозадачность (зеленые темы)
    Пользовательские переопределения ввода-вывода, чтобы мои интерактивные сеансы REPL можно было сохранить на диск
    Переписал основной цикл интерпретатора на Forth
    Переписал цикл виртуальной машины, чтобы не использовать стек C


Внушительный список...

Цитата:
Forth - это динамический язык, в котором единственным типом является «16-битное число», и вы можете делать с этим числом все, что хотите. Это, конечно, чертовски опасно, но если вы пишете код, у которого нет шансов обрабатывать произвольный враждебный ввод из Интернета

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

Цитата:
Честно говоря, я не знаю, есть ли лучший способ понять Forth, чем просто попытаться создать свой собственный и обратиться к другим реализациям и документам Forth, когда вы застряли.


Совершенно согласен.


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения: Re: Статья What the Hell is Forth (Что, черт возьми, такое Ф
СообщениеДобавлено: Ср дек 30, 2020 13:35 
Не в сети

Зарегистрирован: Чт янв 07, 2016 19:14
Сообщения: 1066
Благодарил (а): 1 раз.
Поблагодарили: 8 раз.
Насчет разбиения задачи на кучу мелких слов тут засада уже с другого боку начинается.

Надо всей этой мелкой шелупони имена придумывать!

_________________
Цель: сделать 64-битную Нову под Винду


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения: Re: Статья What the Hell is Forth (Что, черт возьми, такое Ф
СообщениеДобавлено: Ср дек 30, 2020 17:30 
Не в сети
Administrator
Administrator
Аватара пользователя

Зарегистрирован: Вт май 02, 2006 22:48
Сообщения: 7123
Благодарил (а): 17 раз.
Поблагодарили: 119 раз.
Victor__v писал(а):
Надо всей этой мелкой шелупони имена придумывать!

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


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения: Re: Статья What the Hell is Forth (Что, черт возьми, такое Ф
СообщениеДобавлено: Ср янв 06, 2021 23:12 
Не в сети

Зарегистрирован: Пн янв 07, 2013 22:40
Сообщения: 1341
Благодарил (а): 3 раз.
Поблагодарили: 48 раз.
Статья: Почему конкатенативное программирование имеет значение
Why Concatenative Programming Matters
Содержание по версии Google Translate
Код:
12 февраля 2012 г.
Почему конкатенативное программирование имеет значение
Введение

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

Прежде всего, похоже, существуют некоторые разногласия по поводу того, что на самом деле означает термин «конкатенативный». Этот ответ Нормана Рэмси о переполнении стека заходит так далеко, что говорит:

    «… Бесполезно использовать слово« конкатенативный »для описания языков программирования. Это место похоже на частную игровую площадку Манфреда фон Туна . Нет реального определения того, что составляет конкатенативный язык, и нет зрелой теории, лежащей в основе идеи конкатенативного языка ».

Это довольно жестко и, что ж, неправильно. Не совсем неправильно, ум, скорее неправильно. Но неудивительно, что такая дезинформация распространяется, потому что конкатентивное программирование не так хорошо известно. (Я стремлюсь изменить это.)

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

Одна из проблем функционального программирования состоит в том, что часто рекламируемые преимущества - неизменность, ссылочная прозрачность, математическая чистота и т. Д. - не сразу начинают применяться в реальном мире. Причина, по которой «Функциональное программирование имеет значение» было необходимо в первую очередь, заключалась в том, что функциональное программирование было неправильно охарактеризовано как парадигма негативов - без мутации, без побочных эффектов - поэтому все знали, что вы не можете сделать, но мало кто понимал, что ты мог бы .
Аналогичная проблема существует и с конкатенативным программированием. Взгляните на введение в Википедии:

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

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

    Как работает конкатенативное программирование
    Как работает набор на конкатенативном языке
    Насколько эффективны конкатенативные языки
    Какие конкатенативные языки не подходят
    Куда обратиться за дополнительной информацией


А теперь приступим!

♦ ♦ ♦

Основы

В прикладном языке функции применяются к значениям для получения других значений. λ-исчисление, основа функциональных языков, формализует приложение как «β-редукцию», которая просто говорит о том, что если у вас есть функция ( f   x  =  x  + 1), то вы можете заменить вызов этой функции ( f   y ) на его результат ( y  + 1). В λ-исчислении простое сопоставление ( f   x ) обозначает приложение, но композиция функции должна обрабатываться с помощью явной функции композиции:

    составить: = λ f . λ г . λ х . f ( g x )

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

В конкатенативных языках композиция неявна : « f   g » - это композиция f и g . Однако это не означает, что приложение функции становится явным - оно фактически становится ненужным . И, как оказалось, этот странный факт значительно упрощает создание, использование и рассуждение этих языков.

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

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


♦ ♦ ♦

Состав

Допустим, мы хотим умножить пару чисел. На типичном конкатенативном языке это выглядит так:

    2 3 ×

В этом есть две странные вещи.
Во-первых, мы используем постфиксную нотацию (2 3 ×), а не префиксную нотацию (× 2 3), которая является общей в функциональном мире, или инфиксную нотацию (2 × 3), которую большинство языков предоставляют для удобства. Ничто не мешает конкатенативному языку иметь инфиксные операторы, но ради единообразия большинство придерживается постфиксной записи: « f   g » означает ( g  ∘  f ), то есть обратное по отношению к композиции функций. На самом деле это довольно приятное обозначение, потому что оно означает, что данные передаются в том порядке, в котором написаны функции.

Во-вторых, мы сказали, что все термины обозначают функции - так что, черт возьми, 2 и 3 там делают? Они действительно кажутся мне ценностями! Но если вы немного наклоните голову, вы также можете увидеть их как функции: значения не принимают аргументов и возвращаются сами . Если бы мы записали входы и выходы этих функций, это выглядело бы так:

    2 :: () → (число)
    3 :: () → (число)

Как вы можете догадаться, « x  ::  T » означает « x имеет тип T », а « T 1  →  T 2 » означает «функцию от типа T 1 к типу T 2 ». Таким образом, эти функции не принимают ввода и возвращают одно целое число. Мы также знаем тип функции умножения, которая принимает два целых числа и возвращает только одно:

    × :: (число, число) → (число)

Как теперь собрать все эти функции вместе? Помните, мы говорили, что « f g » означает (обратную) композицию f и g , но как мы можем составить 2 с 3, если их входы и выходы не совпадают? Вы не можете передать целое число функции, которая не принимает аргументов.

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

    2 :: ∀ . ( ) → ( , целое) 3 :: ∀ . ( ) → ( , целое) × :: ∀ . ( A , int, int) → ( A , int)

«∀ A ». означает «Для всех А » - в этих примерах, даже если в А есть запятые. Итак, теперь смысл выражения «2 3» ясен: это функция, которая не принимает входных данных и возвращает как 2, так и 3. Это работает, потому что, когда мы составляем две функции, мы сопоставляем выход одной с входом прочее, поэтому начнем со следующих определений:

    2 :: ∀ . ( ) → ( , Int ) 3 :: ∀ Б . ( B ) → ( B , интервал)

Подбираем типы:

    ( A , число ) = ( B )

Подставляя, мы получаем новый полиморфный тип для 3 внутри выражения:

    3 :: ∀ С . ( C , интервал ) → ( C , интервал , интервал)

Это соответствует неполиморфному типу:

    3 :: ∀ A . ( A , int ) → ( A , int , int) = ∀ B. B → ( B , интервал)

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

    2 3 :: ∀ . ( A ) → ( A , интервал, интервал)

Проделав такой же процесс умножения, мы получим:

    2 3 :: ∀ . ( ) → ( , Int, Int) × :: ∀ B . ( Б , Int, Int) → ( B , INT) A = B 2 3 × :: ∀ . ( А ) → ( А , целое)


Это правильно: выражение «2 3 ×» не требует ввода и дает одно целое число. Ух! Для проверки работоспособности обратите внимание, что эквивалентная функция «6» имеет тот же тип, что и «2 3 ×»:

    6 :: ∀ . ( А ) → ( А , целое)

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

♦ ♦ ♦

Крутые вещи

В приведенном выше примере мы работали слева направо, но, поскольку композиция ассоциативна, вы можете делать это в любом порядке. С математической точки зрения ( f  ∘  g ) ∘  h = f  ∘ ( g  ∘ h ). Так же, как «2 3 ×» содержит «2 3», функцию, возвращающую два целых числа, она также содержит «3 ×», функцию, которая трижды возвращает свой аргумент:

    3 × :: (число) → (число)

(С этого момента я буду опускать бит ∀ в сигнатурах типов, чтобы их было легче читать.)

Итак, мы уже можем тривиально представить частичное приложение функции. Но на самом деле это огромная победа с другой стороны. Аппликативные языки должны иметь определенную ассоциативность для приложения функций (почти всегда слева направо), но здесь мы свободны от этого ограничения. Компилятор для статически типизированного конкатенативного языка может буквально:

    Разделите программу на произвольные сегменты
    Скомпилировать каждый сегмент параллельно
    Составьте все сегменты в конце

Это невозможно сделать ни с одним другим языком. При конкатенативном программировании параллельный компилятор - это старый добрый map-reduce!

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

Это основная причина того, что конвейеры Unix настолько мощны: они образуют рудиментарный язык конкатенативного программирования на основе строк. Вы можете отправить вывод одной программы в другую ( |); отправлять, получать и перенаправлять несколько потоков ввода-вывода ( n<, 2&>1); и больше. В конце концов, конкатенативная программа - это поток данных от начала до конца. И снова именно поэтому конкатенативная композиция записывается в «обратном» порядке - потому что на самом деле она идет вперед :

+---+
| 2 |
+---+
  |
  |   +---+
  |   | 3 |
  |   +---+
  |     |
  V     V
+---------+
| *       |
+---------+
  |
  V



♦ ♦ ♦

Реализация

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

    Java Virtual Machine на компьютере и мобильном телефоне
    CPython байткод интерпретатор , что полномочия BitTorrent, Dropbox, и YouTube
    Язык описания страниц PostScript, на котором работают многие принтеры мира
    Язык Forth , с которого все началось, который до сих пор пользуется популярностью во встроенных системах

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

Это стопка. Ага. Просто стопка.

Рассмотрим информацию, передаваемую между функциями в выражении «2 3 × 4 5 × +», которое, если вы не знаете свой постфикс, равно (2 × 3 + 4 × 5):

Функция    Вывод

   ()
2    (2)
3    (2, 3)
×    (6)
4    (6, 4)
5    (6, 4, 5)
×    (6, 20)
+    (26)

Двигаясь слева направо в выражении, всякий раз, когда мы встречаем «значение» (помните: самовозвратная функция с нулевым значением), мы помещаем его результат в стек. Всякий раз, когда мы сталкиваемся с «оператором» (ненулевой функцией), мы извлекаем его аргументы, выполняем вычисление и отправляем его результат. Другое название постфикса - обратная польская нотация , которая добилась большого успеха на рынке калькуляторов на каждом калькуляторе HP, проданном в период с 1968 по 1977 год, а также во многих последующих.

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

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

♦ ♦ ♦

Бесточечные выражения.

Хорошим стилем функционального программирования считается написание функций в безточечной форме без ненужного упоминания переменных ( точек ), над которыми работает функция. Например, « f   x   y  =  x  +  y » можно записать как « f  = (+)». Более ясно и менее однообразно сказать « f - это функция сложения», чем « f возвращает сумму своих двух аргументов».

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

Однако, если функциональные программисты действительно считают, что бесточечный стиль идеален, им не следует использовать прикладные языки! Допустим, вы хотите написать функцию, которая сообщит вам количество элементов в списке, удовлетворяющих предикату. В Haskell, например:

countWhere :: (a -> Bool) -> [a] -> Int
countWhere predicate list = length (filter predicate list)


Это довольно просто, даже если вы не слишком знакомы с Haskell. countWhereвозвращает lengthсписок, который вы получаете, когда вы filterизвлекаете элементы, listкоторые не удовлетворяют predicate. Теперь мы можем использовать это так:

countWhere (>2) [1, 2, 3, 4, 5] == 3


Мы можем написать это двумя способами в бессмысленном стиле, опуская predicateи list:

countWhere = (length .) . filter
countWhere = (.) (.) (.) length filter


Но смысл странного многократного самостоятельного применения оператора композиции ( .) не обязательно очевиден. Выражение - (.) (.) (.)эквивалентно (.) . (.)инфиксному синтаксису - представляет функцию, составляющую унарную функцию ( length) с двоичной функцией ( filter). Иногда пишут этот тип композиции .:, и этого можно ожидать:

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)

countWhere = length .: filter


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

В частности, не существует единого способа составления функций с разным количеством аргументов или результатов. Для того, чтобы даже приблизиться к тому , что в Haskell, вы должны использовать curryи uncurryфункцию явно обернуть вещи в кортежи. Независимо от того, что вам нужны разные комбинаторы для составления функций разных типов, потому что в Haskell нет стекового полиморфизма для аргументов функции, и он по сути не может.

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

define countWhere [filter length]
(1, 2, 3, 4, 5) [2 >] countWhere


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

+-----------------+
| (1, 2, 3, 4, 5) |
+-----------------+
    |
    |               +-------+
    |               | [2 >] |
    |               +-------+
    |                 |
+---|-----------------|-----+
|   |    countWhere   |     |
|   V                 V     |
| +-----------------------+ |
| | filter                | |
| +-----------------------+ |
|   |                       |
|   V                       |
| +--------+                |
| | length |                |
| +--------+                |
|   |                       |
+---|-----------------------+
    |
    V


Когда вы строите такую ​​диаграмму, просто следуйте нескольким простым правилам:

    Каждый блок - это одна функция
    Блок занимает столько столбцов, сколько необходимо для его входов или выходов, в зависимости от того, что больше
    Добавляя блок, поместите его в крайний правый столбец:
        Если это не требует ввода, добавьте столбец
        Если не хватает стрелок, чтобы соответствовать блоку, программа плохо типизирована


♦ ♦ ♦

Цитаты.

Обратите внимание на использование скобок для предиката [2>] в предыдущем примере? Помимо композиции, функцией, завершающей конкатенативный язык, является цитирование , которое позволяет отложить композицию функций. Например, «2>» - это функция, которая возвращает, превышает ли ее аргумент 2, а [2>] - это функция, возвращающая «2>».

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

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

    filter :: (список T , T → bool) → (список T )

С этим можно делать все, что угодно. Вы можете написать функцию, которая применяет цитату к некоторым аргументам, не зная, что это за аргументы:

    применяются :: ∀ A B . ( А , А → В ) → ( В )

Вы можете написать функцию для объединения двух цитат в новую:

    Сотрозе :: ∀ Б С . ( A → B , B → C ) → ( A → C )

И вы можете написать так, чтобы преобразовать функцию в цитату, которая ее возвращает:

    цитата :: ( T ) → (() → T )


♦ ♦ ♦

Темная сторона

Допустим, вы хотите преобразовать математическое выражение в конкатентивную форму:

    f   x   y   z  =  y 2  +  x 2  - | y |

Здесь всего понемногу: он несколько раз упоминает один из своих входов, порядок переменных в выражении не соответствует порядку входов, и один из входов игнорируется. Итак, нам нужна функция, которая дает нам дополнительную копию значения:

    dup :: ( Т ) → ( Т , Т )

Функция, которая позволяет нам переупорядочивать наши аргументы:

    своп :: ( Т 1 , Т 2 ) → ( Т 2 , Т 1 )

И функция, позволяющая игнорировать аргумент:

    падение :: ( T ) → ()

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

    join 2 :: ( T 1 , T 2 ) → (() → ( T 1 , T 2 ))
    join 2 = обмен котировками обмен цитатами составить

Или функция, которая поворачивает свои три аргумента влево:

    rot 3 :: ( T 1 , T 2 , T 3 ) → ( T 2 , T 3 , T 1 )
    rot 3 = присоединиться 2 обменять цитату составить применить

И, эй, благодаря цитатам, также легко объявить свои собственные управляющие структуры:

define true  [[drop apply]]      # Apply the first of two arguments.
define false [[swap drop apply]] # Apply the second of two arguments.
define if    [apply]             # Apply a Boolean to two quotations.


И использовать их, как- на самом деле, просто это -он имеет регулярную функцию:

["2 is still less than 3."]    # "Then" branch.
["Oops, we must be in space."] # "Else" branch.
2 3 <                          # Condition.
if print                       # Print the resulting string.


Эти конкретные определения для trueи falseбудут знакомы любому , кто использовал булевы в Х-исчислении. Логическое значение - это цитата, поэтому оно ведет себя как обычное значение, но содержит двоичную функцию, которая выбирает одну ветвь и отбрасывает другую. «Если-то-еще» - это просто применение этой цитаты к конкретным ветвям.

В любом случае, возвращаясь к математике, мы уже знаем тип нашей функции ((int, int, int) → (int)), нам просто нужно понять, как туда добраться. Если мы построим диаграмму того, как данные проходят через выражение, мы можем получить следующее:

   +---+  +---+  +---+
   | x |  | y |  | z |
   +---+  +---+  +---+
     x      y      z
     |      |      |
     |      |      V
     |      |    +------+
     |      |    | drop |
     |      |    +------+
     |      V
     |    +----------+
     |    | dup      |
     |    +----------+
     |      y      y
     |      |      |
     |      |      V
     |      |    +----------+
     |      |    | dup      |
     |      |    +----------+
     |      |      y      y
     |      |      |      |
     |      |      V      V
     |      |    +----------+
     |      |    | *        |
     |      |    +----------+
     |      |     y^2
     |      |      |
     |      V      V
     |    +----------+
     |    | swap     |
     |    +----------+
     |     y^2     y
     |      |      |
     |      |      V
     |      |    +-----+
     |      |    | abs |
     |      |    +-----+
     |      |     |y|
     |      |      |
     V      V      V
   +-----------------+
   | rot3            |
   +-----------------+
    y^2    |y|     x
     |      |      |
     |      |      V
     |      |    +----------+
     |      |    | dup      |
     |      |    +----------+
     |      |      x      x
     |      |      |      |
     |      |      V      V
     |      |    +----------+
     |      |    | *        |
     |      |    +----------+
     |      |     x^2
     |      |      |
     |      V      V
     |    +----------+
     |    | swap     |
     |    +----------+
     |     x^2    |y|
     |      |      |
     |      V      V
     |    +----------+
     |    | -        |
     |    +----------+
     |   x^2-|y|
     |      |
     V      V
   +----------+
   | +        |
   +----------+
y^2+x^2-|y|
     |
     V

Итак, нашу последнюю функцию можно записать:

    f = drop dup dup × перестановка abs rot 3 dup × перестановка - +

Ну… это отстой.

♦ ♦ ♦

Более легкое замечание.

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

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

    «Из 38 088 определений слов и методов в исходном коде Factor и его среде разработки на момент написания этой статьи 310 были определены с именованными параметрами». - Фактор: язык программирования на основе динамического стека.

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

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

    квадрат = dup ×
    f = падение [квадрат] [абс] би - [квадрат] провал +

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

♦ ♦ ♦

Куда отсюда

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

Вы видели, что конкатенативное программирование - это такая же парадигма, как и любая другая, с настоящим определением и своими плюсами и минусами:

    Конкатенативные языки просты и последовательны («все есть»)
    Они поддаются программированию потока данных
    Языки стека хорошо изучены и имеют хорошую производительность
    Вы можете легко свернуть собственные контрольные структуры
    Все написано бессмысленно (хорошо это или плохо)


Если вы хотите попробовать зрелый, практичный конкатенативный язык, загляните в Factor и в официальный блог его создателя Славы Пестова. Также см. Cat для получения дополнительной информации о статической типизации на конкатенативных языках.

Я уже некоторое время лениво работал над небольшим конкатентивным языком под названием Kitten . Он динамически типизирован и компилируется в C, поэтому вы можете запускать его где угодно. Мне нужен был язык, который я мог бы использовать для сайта на общем хосте, где установка компиляторов меня раздражала. Это показывает вам степень моих языковых фанатиков - я лучше потрачу часы на написание языка, чем двадцать минут на выяснение того, как установить GHC на Bluehost.

В любом случае, реализация едва ли завершена, чтобы можно было поиграть. Не стесняйтесь просматривать источник, пробовать его и предлагать отзывы. Для его создания вам понадобятся GHC и GCC, и я полагаю, что он лучше всего работает в Linux или Unix, но не должно быть особо ужасной несовместимости.

Это также хорошее время, чтобы упомянуть, что я работаю над более серьезным языком под названием Magnet, о котором я упоминал в своей последней статье о том, как происходит программирование . Он в основном конкатенативный, но для удобства имеет прикладной синтаксис и в значительной степени опирается на двойную магию сопоставления с образцом и обобщенных алгебраических типов данных. Черт, половина причины, по которой я написал эту статью, заключалась в том, чтобы предоставить основу для Magnet. Так что ждите больше статей об этом.

Изменить (20 апреля 2013 г.) Приведенная выше информация больше не точна. Котенок сейчас переписывается; работа, которая была проделана над Magnet, была включена. Kitten теперь статически типизирован и до завершения компиляции интерпретируется.

И это о нем. Как всегда, не стесняйтесь писать мне на evincarofautumn@gmail.com, чтобы подробно рассказать о чем угодно. Удачного кодирования!
По Джон Парди в 12:02 AM Отправить по электронной почте BlogThis! Поделиться в Twitter Поделиться в Facebook Поделиться в Pinterest
Теги: Вычисления , Конкатенативный , Котенок , Язык , Магнит , Программирование , Веб


P.S. После статьи есть ещё некоторое обсуждение в комментариях.


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 5 ] 

Часовой пояс: UTC + 3 часа [ Летнее время ]


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 9


Вы не можете начинать темы
Вы можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
phpBB сборка от FladeX // Русская поддержка phpBB