«Бросая камешки в воду, смотрите на круги, которые они образуют; иначе такие метания будут пустой забавой».
К.
Прутков Однажды, бесцельно тратя время и деньги своего работодателя на серфинг в Интернете, я наткнулся на описание языка В любое время и на какое-то время был очарован.
Язык поражает своей безумной простотой.
Его принципы: 1) Строки программного кода когда-нибудь обязательно будут выполнены, но порядок их выполнения не имеет ничего общего с порядком, в котором они написаны.
2) Переменные? Мы даже не контролируем порядок исполнения; нам не нужны никакие переменные.
3) Структуры данных? Ты шутишь! То есть программа рассматривается как набор (пул) строк, подлежащих исполнению, а интерпретатор выбирает оттуда случайным образом строку, выполняет ее команды и выбрасывает ее из пула.
И так до тех пор, пока в бассейне ничего не останется.
Надо признать, что автор этого безумия практически придерживался концепции.
Почти, потому что порядок выполнения в программе еще можно организовать, так же, как можно создавать переменные, используя возможность добавления строк в пул исполняемых файлов.
Итак, в языке существуют следующие конструкции: Строка кода добрый
Выражения состоят из команд, разделенных запятыми.line-number statement;
Среди них: Номер строки line-number#number-of-times-to-add/remove
Например, команда в строке 1 1 4,5#3,-6;
Добавит 1 строку с номером 4, 3 строки с номером 5 в пул выполнения и удалит строку с номером 6 из пула.
Обратите внимание, что программа с бесконечным циклом будет выглядеть лаконично и, следовательно, гениально.
1 1;
Интерпретатор выберет из пула первую строку (другой строки там нет) и добавит ее обратно в пул.
Кстати, очень полезная конструкция, как будет видно далее.
Вывод на дисплей Просто команда печати, которая выводит на экран то, что указано в скобках.
Помимо этих команд существуют следующие конструкции: отложить Если выражение в скобках возвращает true, команда не будет выполнена, а строка будет возвращена обратно в пул.
Например 1 defer (2) 3;
Пока строка 2 присутствует в пуле, команда добавления строки 3 в пул не будет выполнена.
снова Если выражение в скобках возвращает true, команда будет выполнена, но строка будет возвращена в пул.
Например: 1 again (2) 3;
Строка 3 всегда будет добавляться в пул, но если в пуле есть строка 2, то строка 1 после этого не будет удалена из пула.
Обратите внимание, что выражение (2) эквивалентно ( N(2) > 0 ), где N() — функция, возвращающая количество строк номеров вызовов в пуле.
забывать Если выражение в скобках возвращает true, команда не будет выполнена, а строка будет удалена из пула.
Например: 1 forget(2) 3;
Автор языка удалил это выражение из языка, но мне пришлось его вернуть, как описано ниже.
Опять же, отложить и забыть можно комбинировать, но каждый модификатор должен появляться в строке только один раз.
Собственно всё, весь язык.
На сайте есть две программы с описанием языка:
99 бутылок пива 1 defer (4 || N(1)<N(2) && N(2)<N(3)) print(N(1)+" bottles of beer on the wall, "+N(1)+" bottles of beer,");
2 defer (4 || N(1)==N(2)) print("Take one down and pass it around,");
3 defer (4 || N(2)==N(3)) print(N(1)+" bottles of beer on the wall.");
4 1#98,2#98,3#98;
и Числа Фибоначчи (вычисляет ряд Фибоначчи от 1-го до 100-го элемента.
ну.
если подождать): 1 again (1) defer (3 || N(1)<=N(2) || N(7)>99) 2#N(1),3,7;
2 again (2) defer (3 || N(2)<=N(1) || N(7)>99) 1#N(2),3,7;
3 defer (5) print(N(1)+N(2));
4 defer (5) print("1");
5 4,-3,7;
6 defer (4) 3;
7 7;
8 defer (N(7)<100) -1#N(1),-2#N(2),-7#100,-3;
9 defer (3 || 6) 1,3;
Если первая программа — чистое баловство, то вторая делает что-то значимое, и это удивительно.
Казалось бы, ничего хорошего из такого баловства выйти не может по определению.
Однако вот оно.
И поэтому мне хотелось что-то со всем этим сделать.
Результат оказался несколько неожиданным, но обо всём по порядку.
Сразу будет очевидно, что эти программы неудобны: чтобы вычислить ряд Фибоначчи не до сотого, а, скажем, до 20-го элемента, нужно изменить текст в четырех местах программы.
Это немного раздражает. Давайте удостоверимся, что программа может принимать входные параметры.
Например вот так: 1 again (1) defer (3 || N(1)<=N(2) || N(7)>( @1 - 1 )) 2#N(1),3,7;
2 again (2) defer (3 || N(2)<=N(1) || N(7)>( @1 - 1 )) 1#N(2),3,7;
3 defer (5) print(N(1)+N(2));
4 defer (5) print("1");
5 4,-3,7;
6 defer (4) 3;
7 7;
8 defer (N(7)<@1) -1#N(1),-2#N(2),-7#100,-3;
9 defer (3 || 6) 1,3;
Сохраним текст программы в файл fib.src. Вызов программы будет выглядеть так: java -cp Whenever.jar Whenever fib.src 20
Уже хорошо.
Чтобы закрепить свой успех, я написал программу по поиску наибольшего общего делителя двух натуральных чисел, вот она: 1 defer(2) again(N(3) - (N(3)/N(4))*N(4) != 0) 6#N(3),-3#N(3),3#N(4),-4#N(4),4#(N(6) - (N(6)/N(3))*N(3)),-6#N(6);
2 3#( @1 - 1 ),4#( @2 - 1 ),-6;
3 3;
4 4;
5 defer(1) print("NOD of " + @1 + " and " + @2 + " is " + N(4)),-3#N(3),-4#N(4);
6 6;
При вызове программы нужно указать два числа, сначала большее, затем меньшее.
Давайте проанализируем эту программу; Строки 3 и 4 используются как ячейки памяти для хранения двух чисел.
Точнее, в качестве переменных используется количество вхождений соответствующих строк в пул.
Строка 6 используется в качестве промежуточной переменной таким же извращенным образом (другого пути язык не оставляет).
Строка 2 запускает весь процесс.
Он добавляет в пул N строк 3 и M строк 4 и удаляет из пула строку 6. После чего он удаляется из пула навсегда.
Строка 1 фактически выполняет всю работу.
Выполнение строки 1 откладывается до тех пор, пока строка 2 не появится в пуле (defer(2)).
Кроме того, строка НЕ выбрасывается из пула до тех пор, пока не будет найден НОД (снова).
Выражение в скобках даст остаток от числа строк 3, разделенного на количество строк 4 (используется целочисленная математика).
Фактические действия в строке 1 выполняются слева направо: В пул добавляется столько 6 строк, сколько в пуле на этом этапе имеется 3 строки.
Все строки 3 удалены из пула Добавляется столько строк, сколько на данный момент имеется 4 строк (эти две команды можно объединить в одну -3#(N(3) - N(4)) Удалить все строки 4 Добавляем 4 строки в сумме == остаток от деления большего числа на меньшее.
Опять же, эти две команды можно объединить в одну.
Удалить все 6 строк из пула (скрипач не нужен) Строка 5 печатает результат и очищает за собой все, что осталось из пула.
Его выполнение задерживается до тех пор, пока строка 1 присутствует в пуле, и она останется там до тех пор, пока не будет найден НОД.
Замечу, что в оригинальной версии языка печать нельзя совмещать с другими командами; там надо было бы написать 5 defer(1) print("NOD of " + @1 + " and " + @2 + " is " + N(4));
.
7 defer(5) -3#N(3),-4#N(4);
Да, я подумал.
Сделаем так, чтобы при добавлении строки в пул ей можно было передавать параметры.
Поскольку в синтаксисе уже используются круглые скобки и запятые и мне не хотелось слишком всё переписывать, я использовал квадратные скобки и знак процента % для разделителя параметров.
Здесь возникает одна тонкость.
При добавлении строки в пул он должен фактически создавать еще одну строку с новым номером, а не увеличивать количество выполнений для существующей.
(На самом деле для «чистоты» необходимо увеличить количество исполнений строк с одинаковыми параметрами, но я это пока не реализовал).
Здесь у меня вдруг появилось еще одно измерение, еще одна переменная — номер текущей строки.
Чтобы иметь возможность его использовать, я ввел еще одно ключевое слово — self. Ура! Теперь мы можем сделать что-то полезное.
Например, перепишем тот же ряд Фибоначчи: 1 forget(self>@3) print("iter: " + self + " value: " + @1 ),1[@1+@2%@1%@3];
В программу передаются три параметра – 1, 0 и номер числа, до которого необходимо произвести расчет. К счастью, важен только третий параметр, и программу можно переписать так, чтобы она принимала только его.
Тогда, кстати, от себя можно будет избавиться, но программа жутко разрастется до двух целых строк, а это не очень приятно.
Примечания: Мне пришлось 1) восстановить права на забвение и 2) убедиться, что печать выполнялась вместе с другими командами (в оригинале могли быть только команды печати или другие).
А вот НОД: 1 forget( @2 == 0 ) 1[@2%(@1 - (@1 / @2) * @2)],2[@2%(@1 - (@1 / @2) * @2)];
2 forget( @2 > 0 ) print("Nod is: " + @1 );
Опять же, можно сделать выполнение независимым от номера большего параметра, но мне лень.
И для начала проверьте, является ли число простым: 1 defer(5) forget( @2 == 0 || @2 == 1 ) 4[@1%(@2 - 1)],2[@1%(@2 - 1)],3[@1%(@2 - 1)];
2 defer(5) forget( @2 == 0 || @1 != (@1 / @2) * @2 || @1 == @2 || @2 == 1 ) print( "Number " + @1 + " is not prime");
3 defer(5) forget( @2 != 1 ) print( "Number " + @1 + " is prime");
4 defer(5) forget( @2 == 0 || @1 == (@1 / @2) * @2 ) 1[@1%@2];
5 1[@1%@1];
Здесь я использую еще один прием: если параметр не указан, он становится нулевым.
Это пирожки с котятами.
Что-то мне подсказывает, что я могу попробовать решить комбинаторные задачи, используя эту фигню.
Но сейчас мне лень придумывать решения.
Хотя… Теги: #программирование #Функциональное программирование #когда угодно #Аномальное программирование #Алгоритмы
-
Преимущества Использования Планшетного Пк
19 Oct, 24 -
Минилинукс
19 Oct, 24 -
Windows Больше Не Продается В России?
19 Oct, 24