Шизоїдна мова програмування самонавчальних алгоритмів «Автор»

Я хочу розповісти світу принципово нову мову програмування, аналогів якій немає в усьому світі.


Свого часу я був, одержимий ідеєю штучного інтелекту. Коли я став програмістом, я зрозумів, що все не так просто, і можна сказати навіть набагато складніше, ніж здавалося. Я не переставав працювати над програмами і освоював всі відомі нині мови програмування. Я намагався перекласти на машину якомога більше власних уявних процесів, і виявилося, що вони займали великі обсяги коду і самі вміли дуже мало. Я намагався змусити програми вчиться, робити висновки, і використовувати їх надалі. Але всі мови програмування мають однаковий недолік - вони не можуть розглядати алгоритми, як дані. Програми не можуть вчитися, з того, що вони не мають доступу до самих себе.

Так я прийшов до того, що потрібно зробити «Сі» подібний інтерпретатор, програми на якому будуть мати можливість доступу до самих себе. Ідея проста - щоб вчитися, потрібно змінюватися, а для цього потрібно мати доступ до власного коду. Тепер навчання стає хоча б можливим. Після будь-якого втручання в тіло алгоритму, воно стає відмінним від свого вихідного коду програми і ці зміни потрібно зберегти. Інтерпретатор стає ніби автором нової програми. Тому мова використовує наступну культуру. Файл з кодом «* .txt» має свій дублікат «* .code», який створюється автоматично. Така пара називається модулем. При завантаженні модуля «Автор» вибирає, з пари, файл, записаний пізніше, а в разі відсутності одного з пари, і вибирати не доводиться. Таким чином, вихідний код завжди залишається недоторканим, а всі зміни, які відбулися, будуть видимі програмісту у файлі дублікаті. Стартовий модуль повинен мати точку входу (main) і для запуску потрібно перетягнути мишкою файл на «author.exe». Будь-який модуль може запитувати про дозавантаження інших модулів (# include < me.txt >). Також модулі можна завантажувати і вивантажувати в процесі виконання коду командою include («me.txt») і uninclude («me.txt»). Після завершення роботи алгоритму «Автор» вивантажує стартовий модуль. Кожен модуль має лічильник кількості модулів, які його запросили, і в разі досягнення нуля, додаток вивантажує свій код у файл дублікат. До перезапису файла дубліката модуля доходить тільки тоді, коли в ньому (модулі) сталася хоч одна зміна або ж дубліката не було зовсім. Таким чином можна як завгодно прописувати з'єднання додатків, не задумуючись про те, яким буде дерево з'єднань, додаток завжди буде в пам'яті в одному екземплярі. Головне не підключити самого себе.

Синтаксис мови «Сі» подібний. Додаток містить багато функцій. У файлах програма представлена як текст. Але при завантаженні, відбувається перетворення і розвиток коду до динамічної структури схеми алгоритму. Для кожної функції створюється потоковий граф і вже до нього отримує доступ програма. При вивантаженні модуля відбувається зворотне перетворення, схеми в текст програми. «Автор» навіть намагається дотримуватися стилю програмування, щоб код був не в одну сходинку. Таким чином, можна, наприклад, зробити функцію, яка зможе підрахувати кількість умов або циклів у зазначеній функції, наприклад в самій собі. Вважаю читачеві буде цікаво поглянути на таку функцію, з цього, не дивлячись на заборону модератора, я наведу її:

//noproblem.txt//Програма
підрахунку власних циклів та умовоvoid
main () {
f = getFunction (getThisFunc^ Name ()) ;//доступ до себе
tree = f.export () ;//перетворити на дерево алгоритму
counterIF = 0;
counterWHILE=0;
counterDO=0;
counterFOR = 0 ;
//організація обходу дерева вкладених циклів і умов
access = {};
do{
  n=tree.getRowSize(access);
access.push(n);
while(access.size()){
   n=access.pop();
--n;
if(n<0)continue;
access.push(n);
sub=tree.getSub(access);
type="""";
if(typeof(sub)==""program"")type=sub.typeof();
if(type==""if"")++counterIF;
if(type==""while"")++counterWHILE;
if(type==""do"")++counterDO;
if(type==""for"")++counterFOR;
break;
   }
  }while(access.size());
trace ("В алгоритмі функції" "+ f.getName () .export () +" "міститься:"");
trace ("Умовних гілок: ""+counterIF);
trace ("Циклів while: ""+counterWHILE);
trace ("Циклів do: ""+counterDO);
trace ("Циклів for: ""+counterFOR);
getstring
();
} Алгоритм функції «main» містить:
Умовних гілок: 6Циклів
while: 1Цикла
do: 1Циклів
for: 0

Змінні в мові не мають прив'язки до типу подібно PHP і JS. Їх навіть необов'язково оголошувати. Тип, вказаний при оголошенні, служить не більш ніж коментар. Змінна створюється також при вживанні її в конструкції для запису (а = 0;). При зверненні для читання з невідомої змінної повертається тип «void». Значення будь-якої змінної може мати тип: void, int, float, double, digit, char, string, interval, vector (безліч), set (унікальна безліч), map (асоціативний масив), program (дерево алгоритму), function (схема алгоритму), graf, module. У мові також присутні покажчики, але у зв'язку з шизоїдною особливістю, вони організовані як рядок з даними для доступу до змінної. Змінна може містити курсор на саму себе (p = & p; p = * * * * p;). Можна взяти рядок з назвою типу значення. «typeof (typeof (#)) = =» string «» - завжди істина.

У мові можна використовувати особливий операнд «#». Він повертає тип «void» і служить символічним позначенням прихованого блоку, який, як і будь-який інший операнд, можна поміняти на конструкцію дерева операторів, якої завгодно складності. При потраплянні значення «void» в умову тернарного оператора, інтерпретатор, кожен раз, на момент виконання, випадковим чином визначається між істиною і ложею. Вирази "#? 1:0 «і» rand ()% 2? 1:0 "- аналогічні. Але є серйозна різниця між "if (#);" і "if (#? 1:0);».

При потраплянні значення типу «void» в умову, настає шизофренія. Поточна точка виконання алгоритму, в цей момент, ділиться на дві. Одна переходить по істині, інша по брехні. У подальшій інтерпретації, в міру необхідності, ділиться на дві і вся карта пам'яті. Таким чином, можна сказати, що кожна точка виконання має свою окрему карту пам'яті.

//Приклад шизоїдної програми:
main(){
 n=0;
if(#)n=1+1;
n+=10;
trace(n);
}
10
12

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

Поділ точки виконання можна зробити і особливою функцією «rozpad ()». Вона приймає безліч значень і повертає кожне з них у своїй точці виконання. Таким чином, вирази «if (#);» і «if (rozpad ({1,0}));» аналогічні. Вираз «n = rozpad ({1,2,3}) + rozpad ({10,150});» заподіє шизоїдний розпад на шість процесів, з різним значенням змінної «n»: 11, 12, 13, 151, 152 і 153.

Щоб визначитися між усіма варіантами процесів випадковим чином, потрібно викликати функцію «define ();». Таким чином можна звести всі точки виконання в одну.

Настав час зосередитися на шизоїдній особливості мови. Так я називаю можливість обробки неоднозначних даних. Всі сучасні мови програмування використовують змінні для зберігання різноманітних даних, але всі їх значення знаходяться в єдиному варіанті. «Автор» дозволяє зберігати неоднозначні значення в будь-якій змінній. Таким чином, алгоритми написані цією мовою, готові в будь-який момент, зіткнуться з виникненням неоднозначності в процесі вирішення поставленого завдання. Але, часто, буває так, що саме завдання, людською мовою звучить неоднозначно. Наприклад, таке завдання: «Дано масив з 4 - 5 чисел, потрібно його відсортувати».

Неоднозначність алгоритму рішення виходить з самої постановки завдання. По-перше, не вказаний однозначно розмір масиву, а точніше розмір його заданий у двох варіантах. Припустимо, ми можемо самі визначити значення чисел масивів. По-друге, відсортувати масив можливо як за зростанням, так і за вбиванням. Ось як виглядає програма для вирішення даного завдання:

// sort.txt
main(){
 if(#)m={6,2,9,1}; else m={3,7,21,45,8};
m.sort();
if(#)m.reverse();
trace («» Результат: «» + m.export ());
}
Результат: {9,6,2,1}
Результат: {45,21,8,7,3}
Результат: {1,2,6,9}
Результат: {3,7,8,21,45}

Операнд «#» повертає значення «void». При потраплянні його в умовну конструкцію гілки, з якої, між іншим, складається будь-який цикл, точка виконання алгоритму ділиться на дві. Одна переходить по гілці істини, інша по гілці брехні. Для кожної точки виконання існує своя карта пам'яті, тобто відносно однієї позиції інтерпретації, всі змінні містять однозначні значення. Таким чином, після виконання першого рядка програми, комп'ютер ніби ділиться на два. Вони обидва продовжують виконувати одну і тугішу програму, але вже з різними значеннями змінної «m». У другому рядку програми кожен комп'ютер сортує свій масив за зростанням. Далі відбувається поділ кожного комп'ютера ще на два. Одна пара проходить через команду реверсу масиву, а інша - ні. Потім всі комп'ютери видають звіт на свій загальний екран.

Тепер розгляньмо шизоїдний поділ на іншому прикладі. Потрібно знайти, яка комбінація значень змінних «а» і «b» дасть в сумі число 20, притому, що «а» може бути одним з безлічі {5,3,7}, «b» може бути одним з безлічі {15,17} .Програміст відразу побачить тут два вкладених циклу. А ось, як виглядає програма на «Автор»:

void main(){
 a=rozpad({5,3,7});
b=rozpad({15,17});
if(a+b!=20)OFF;
trace(""a+b==""+a+""+""+b);
}
a+b==5+15
a+b==3+17

У першому рядку програми відбувається поділ точки виконання на три паралельних, в кожній з яких функція «rozpad ()» повертає своє значення, одне з заданого множини. У другій сходинці відбувається аналогічний поділ кожної точки виконання на дві. Таким чином, змінні отримають своє поєднання значень з зазначених безліч. Далі йде умова, за якою всі «комп'ютери», у яких «a + b! = 20» переходять до команди «OFF», по якій вони зникають. Таким чином, до команди звіту дійдуть тільки ті «комп'ютери», у яких «a + b = = 20» і значення їх змінних виводиться на загальний екран.

Часто доводиться робити вибір і вибирати одну з альтернатив. Але для того, щоб зробити будь-який вибір, потрібно мати один, чітко сформульований, як завгодно складний, критерій. А як бути, якщо критерій вибору невідомий або не заданий? Випадковий, рівномовірний вибір. Ось універсальний і найпростіший критерій. Для зазначення в алгоритмі потреби вибору одного варіанта процесу, зі своїми даними, в мові є вбудована функція/команда «define ()». Вона здійснює вибір однієї точки виконання з існуючого, на момент виконання її, множини. Як це працює. Поточний процес досягає команди «define ()» і зупиняється. Активним робиться інший, не зупинений, процес зі списку паралельних процесів. Коли всі процеси в списку стають, зупинені, це означає, що настав час зробити вибір одного з них. Вибирається одна точка виконання зі списку, а всі інші процеси закриваються. Вибраний процес запускається на подальше виконання алгоритму. Таким чином, незалежно від порядку обробки паралельних процесів, після виконання команди «define ()» об'єктивно, гарантовано залишається тільки одна точка виконання, зі своїм варіантом даних.

main ()
{trace («» Відомі вітання:"
"); string str = rozpad ({"" Привіт ""., "Здрастуйте". "", "Добрий час доби".
"}); tra
ce(str); d
efine(); trace («» Я вибрав
:
«» + str);} Відомі
вітання
:Привет.
Здравствуйте.Доброго часу
доби. Я вибрав: Привіт.

Наступна програма визначає одну від "ємну та одну додатну кількість з вказаної безлічі чисел:

void main(){
 i=rozpad({-3,-2,-1,0,1,2,3});
if(i<0)define(); else define();
trace(i);
define(-1);
getstring () ;//чекає натискання Enter
}
-
31

У першій сходинці коду відбувається поділ на варіанти. У другій - поділ на дві групи і визначення одного варіанту для кожної з умовних груп. Далі виводимо на екран результат і чекаємо натискання «enter» .Функція «define ()» може приймати одне числове значення. Воно визначає пріоритет визначень. Першим відбудеться те визначення, яке отримає більше число. Типовим значенням є нуль. Якщо порядок визначення не важливий, можна використовувати однакове число. Ось приклад коду, для якого порядок визначення важливий:

void main(){
 int n=rozpad({-2,-1,0,1,2,3});
if(n>=0)define(1);
trace(n);
define(2);
trace(""OK"");
}
-2
-1
OK
2
OK

Слід продовження.

COM_SPPAGEBUILDER_NO_ITEMS_FOUND