Last active
March 6, 2018 00:41
-
-
Save alextretyak/cb36e7f0e5f9b7d7a0700f051e148f4c to your computer and use it in GitHub Desktop.
Заметки по новому языку программирования
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[[[ | |
[[[Хабы: Ненормальное программирование]]] | |
[[[Заголовок: [[[Заметки по новому языку программирования]/]]Каркас нового языка программирования]]] | |
[[[Метки: новый язык программирования, двойная статья]]] | |
]]][[[ | |
Итак, как вы можете наблюдать, эволюция естественных языков завершилась[[[прежде всего потому, что нет [и не предвидится] достаточно ментально сильных людей, которые могли бы как-то повлиять на развитие языков]]] [с наступлением цифровой эпохи] (также как когда-то завершилась эволюция чисел). | |
Подходит к концу и эволюция языков программирования [общего назначения]. | |
В качестве "своего" кандидата на ‘финальный[?]’[‘почему финальный — на мой взгляд энтропия[/хаотичность] (степень неупорядоченности) человеческой деятельности возрастает, и количество недостатков в новых языках [[[программирования]]] будет перевешивать количество преимуществ над предыдущими языками (с учётом затрат перехода на новые языки)’] "конкурс" языков программирования общего назначения я выдвигаю данный язык. | |
Сразу оговорюсь, что на данный момент язык существует лишь в форме/виде идеи и не слишком претендует на ‘практическую реализацию’[‘лично я осилил только лексический анализатор, да и тот, не уверен, что можно будет использовать практически/фактически’]. | |
Но ]]][[[ | |
Наверное, многие подумают, что начинать разработку нового языка программирования, тем более с нуля, независимым разработчиком без поддержки какой-либо корпорации[[[сейчас уже]]] не имеет смысла. Я же считаю, что [[[наступил/]]]сейчас вполне подходящий момент для анализа существующих языков программирования, а также их реализаций, и выделения тех особенностей/черт, которые сделали эти языки успешными. | |
Предлагаемый язык программирования не ставит собой задачу стать лучшим, а быть чуточку логичнее других, и просто достаточно хорошим, чтобы занять определённую нишу и найти своё место в этом, таком многогранном и противоречивом, мире. | |
]]][[[Представьте как здорово было бы иметь возможность писать код веб-сервера в стиле PHP или даже Python (просто `print(...)`\`вывод(...)`) на [[[полноценном[‘причины неполноценности PHP и преимущества статической типизации, RAII, шаблонов и расчётов на этапе компиляции выношу за рамки данной статьи’]]/]]полнофункциональном[‘дополнительно к тому, что поддерживает Go: виртуальные функции, поддержка RAII и шаблонов, возможность выполнения заданного кода на этапе компиляции, поддержка перегрузки функций’] быстро[‘[[[быстрее Go, как старый-добрый Turbo-C или Delphi]]]хотя бы на уровне Go’]-компилируемом языке программирования. | |
]]][[[Также для меня остаётся загадкой, почему до сих пор нет популярного языка программирования, на котором было бы удобно {причём желательно сразу, без установки дополнительных библиотек} и быстро {попробуйте на Python сформировать FullHD картинку} работать с изображениями.](Java:ImageIO)]] | |
[Я так и не смог написать хороший вводный текст к данной статье, поэтому начну просто с примеров.] | |
‘Вот маленький пример кода:’{ | |
Р‘https://gist.githubusercontent.com/alextretyak/cb36e7f0e5f9b7d7a0700f051e148f4c/raw/05db5833b0eefb70825f10e243624d741d58331a/codesample.png’ | |
[Код оформлен на основе идей из ‘следующей статьи’[-вставить ссылку-].] | |
} | |
(Попробуйте угадать значения зарезервированных букв, а также мысленно переписать этот кусочек кода на [[[вашем любимом языке]]]языках программирования, которые вы знаете.) | |
'‘<cut />’' | |
А вот пример побольше (это перевод Python-кода из ‘моей предыдущей статьи’[https://habrahabr.ru/post/349592/]). | |
‘Код’{ | |
#(11l)‘ | |
F calculate_sacred_number() | |
A results = [] | |
L(hash_algorithm) hashlib:algorithms_available // Обходим все доступные хэш-алгоритмы | |
\\ (список включает в себя MD5, SHA...) | |
I.unlikely "shake" C hash_algorithm // Пропускаем алгоритмы SHAKE, так как ... | |
L.continue | |
L(uppercase) 0B..1B // Проверяем варианты написания как строчными, так и ПРОПИСНЫМИ | |
L(space) 0B..1B // Проверяем варианты написания с дефисом и через пробел | |
L(n) 10..99 // Проверяем все двузначные числа | |
A nw = :numbers[n] // Получаем текущее число, записанное словами на англ. | |
I uppercase | |
nw .= upper() | |
I space | |
nw .= replace(‘-’, ‘ ’) | |
A ns = String(n) | |
// Считаем хэш от записанного словами числа, | |
A digest1 = hashlib:(hash_algorithm, nw.encode()).hexdigest() | |
// а также от этого же числа, преобразованного в строку. | |
A digest2 = hashlib:(hash_algorithm, ns.encode()).hexdigest() | |
L 2 // Проверяем целый хэш, а также первую половину хэша | |
// Оба хэша должны начинаться на первую цифру текущего числа | |
// и заканчиваться на вторую цифру. | |
I digest1[0] == ns[0] & digest1[@-1] == ns[1] | |
& digest2[0] == ns[0] & digest2[@-1] == ns[1] | |
results [+]= ns | |
// Берём первую половину хэша | |
digest1 = digest1[0.<@/2] | |
digest2 = digest2[0.<@/2] | |
assert(results.len == 1) // Должно быть только одно "выигравшее" число | |
R results[0] // Возвращаем это число | |
// Based on [https://stackoverflow.com/a/8982279/2692494 ‘How do I tell Python to convert integers into words’] | |
A numbers = "zero one two three four five six seven eight nine".split() | |
numbers [+]= "ten eleven twelve thirteen fourteen fifteen sixteen".split() | |
numbers [+]= "seventeen eighteen nineteen".split() | |
L(tens) "twenty thirty forty fifty sixty seventy eighty ninety".split() | |
L(ones) numbers[0..9] | |
numbers [+]= I ones == "zero" {tens} E tens‘-’ones | |
print(calculate_sacred_number()) | |
’ | |
Как можно заметить, импорт модулей осуществляется неявно — можно просто обращаться к нужной функции пиша `имя_модуля:имя_функции(...)`. То есть, простое обращение к `fs:path:dirname()`, `re:compile()`\`рв:компил()`, `math:log()`\`матем:лог()`, `time:sleep()`\`время:спи()`, `json:load()`, `html:escape()` автоматически импортирует все необходимые модули (`fs`, `fs:path`\`фс:путь`, `re`, `math`, `time`, `json`, `html`). | |
Для сырых строк можно использовать одиночные парные кавычки [[[(их использование позволяет сделать конкатенацию переменных и строк без дополнительного символа для оператора конкатенации)]]]— на мой взгляд строки в таких кавычках выглядят понятнее[‘сразу видно где начинается и где оканчивается строка даже без подсветки [[[синтаксиса]]]кода’]. [Ещё варианты для сырых строк: `'"строка"'`, `''""строка с "'""''`, !‘`строка`’.] | |
} | |
Согласен, что этот пример -‘не слишком впечатляет’ не так уж и проще Python кода, на котором он был основан, но предлагаемый язык, в отличие от Python, компилируемый язык со статической типизацией, дающей преимущества как в производительности (с возможностью без проблем[‘=без оверхеда (как в Python в котором функции лучше не объявлять внутри циклов)’] объявлять функции\closure в теле циклов), так и в безопасности (тот же Cython не обнаруживает такие ошибки на этапе компиляции: `"http://...?rev=" + revision`). | |
‘Язык поддерживает всего 11/12/13 [[[корневых[[[/главных]]]/]]]базовых зарезервированных букв/слов:’{ | |
Т‘ | |
Н‘‘Буквы’ - ‘Слова’ - ‘Пояснение’’ | |
‘‘A’ - - - ‘автоматический тип (аналог `auto` из C++11)’’ | |
‘‘C’ - ‘in’ ‘С’ ‘contained in\содержится’’ | |
‘‘I’ ‘Е’ ‘if’ ‘если’ ‘’’ | |
‘‘E’ ‘И’ ‘else’ ‘иначе’ ‘’’ | |
‘ | | ‘exception’ ‘исключение’ |’ | |
‘‘F’ ‘Ф’ ‘fn’ ‘фн’ ‘объявление функции’’ | |
‘‘L’ ‘Ц’ ‘loop’ ‘цикл’ ‘замена for, while и do-while’’ | |
‘‘N’ ‘Н’ ‘null’ ‘нуль’ ‘’’ | |
‘‘R’ ‘Р’ ‘return’ ‘вернуть’ ‘вернуть *‘р’езультат функции’’ | |
‘‘S’ ‘В’ ‘switch’ ‘выбор’ ‘switch/select’’ | |
‘‘T’ - ‘type’ ‘тип’ ‘объявление нового типа’’ | |
‘‘T()’- ‘typeof’ ‘тип()’ ‘получить тип выражения (‘typeof в GCC’[https://gcc.gnu.org/onlinedocs/gcc/Typeof.html], ‘decltype в C++11’[http://en.cppreference.com/w/cpp/language/decltype])’’ | |
‘‘X’ - - - ‘для дополнительных зарезервированных подслов’’ | |
’ | |
([[[Для тех, кого смущают однобуквенные зарезервированные слова языка — смотрите 3 и 4 колонку в таблице. Но ]]]Исходные файлы на языке должны быть оформлены единообразно — либо [[[с полными]]]используя зарезервированные слова, либо [[[с однобуквенными]]]используя буквы.) | |
Данный/предлагаемый язык дополнительно отличается от других тем, что зарезервированные слова языка [[[не свалены в одну кучу]]]идут не в виде списка слов (как принято в[о всех[?]] других языках программирования), а [[[чётко]]] структурированы[[[/организованы]]] в иерархию, на верхнем уровне которой располагаются 11/12/13 корневых/базовых зарезервированных букв/слов. | |
‘Примеры зарезервированных подслов:’{ | |
Т‘ | |
‘‘I\Е’ ‘I.likely\Е.часто | |
I.unlikely\Е.редко’’ | |
‘‘Е\И’ ‘E.try | |
E.throw | |
E.catch | |
E.try_end (аналог ‘try-else в Python’[https://docs.python.org/3/reference/compound_stmts.html#index-13])’’ | |
‘‘F\Ф’ ‘F.args\Ф.арг | |
F.virtual.new\Ф.виртуал.новая | |
F.virtual.override\Ф.виртуал.переопр | |
F.virtual.final\Ф.виртуал.финал | |
F.virtual.abstract\Ф.виртуал.абстракт | |
F.destructor\Ф.деструктор’’ | |
‘‘L\Ц’ ‘L.break\Ц.прервать | |
L.continue\Ц.продолжить | |
L.again — перейти к началу цикла[[[ | |
L.was_no_break\L.не_был_прерван]]] | |
L.index — номер текущей итерации цикла (начиная с 0) | |
L.next\Ц.след — следующее значение переменной цикла | |
L.prev\Ц.пред — предыдущее значение переменной цикла | |
’’ | |
‘‘S’ ‘S.break | |
S.fallthrough’’ | |
‘‘T’ ‘T.super — для доступа к базовому типу | |
T.enum\Т.переч — объявление перечисления’’ | |
’ | |
} | |
} | |
Н‘Два вида деструкторов’ | |
1. Вызывается при выходе из области видимости (как обычный деструктор в С++ и других языках с поддержкой RAII) — `F.on_scope_exit` (также эту запись можно использовать как аналог ‘defer в Go’[https://golang.org/doc/effective_go.html#defer]). | |
2. Объект разрушается сразу после последнего использования/обращения — `F.destructor`. | |
Основным я предлагаю сделать второй вариант. | |
Это позволит удалять временный файл сразу после работы с ним без явного вызова close(): | |
#(Python)‘ | |
tmpfile, fname = tempfile.mkstemp(text=True) | |
tmpfile = open(tmpfile) | |
r = subprocess.call(cmd, stdout = tmpfile, stderr = tmpfile) | |
tmpfile.seek(0) | |
print(tmpfile.read(), end = '') | |
tmpfile.close() # этот вызов можно не делать, если tmpfile будет разрушен в предыдущей строке | |
os.remove(fname) | |
’ | |
А также избавит от необходимости писать move() в случаях: | |
#(11l)‘ | |
Person p | |
p.name = ... | |
p.age = ... | |
persons.append(p) // в С++ здесь пришлось бы писать persons.push_back(std::move(p)) для оптимальности | |
’ | |
А также позволит избежать копирования при вызове функции sorted()\сортй(): | |
#(Python)‘ | |
for root, dirs, files in os.walk(path): | |
for file in sorted(files): # в C++ бы пришлось писать что-то вроде (files.sort(), files) для избежания копирования массива files | |
... | |
’ | |
Н‘Развивая идею вывода типов (type inference)’ | |
Для начала скажу про такой момент, что в предлагаемом языке [[[существуют]/]]есть такие служебные функции как copy() и share(). | |
Тип умного указателя я предлагаю определять по его использованию (также как тип массива или словаря ‘в Nemerle определяется по типу первого добавленного в него элемента’[http://nemerle.org/About#ID0ESG]). Получается эдакий autounishared_ptr — гибрид unique_ptr и shared_ptr из C++11. Если где либо в коде встречается share(p), тогда p становится shared_ptr-ом, иначе остаётся unique_ptr-ом. | |
[[[ | |
Аналогично определяется где хранить объект, объявленный локально — по его использованию. Если он никуда не добавляется (ни в какой контейнер), а используется только в области, в которой был объявлен, тогда (и также если он не слишком большой) этот объект размещается на стеке, иначе [[[размещается в куче]]]выделяется в динамической памяти. | |
](Уже применяется в Java: | |
>[./Books/Compilers/Akho_Lam_Seti_Ulman_-_Kompilyatory_Printsipy_te.djvu]:‘Разработанная для языка программирования Java оптимизация снижает накладные расходы, например, устраняя излишние проверки диапазонов и выделяя память для объектов, недоступных извне процедуры, в стеке, а не в куче.’ | |
К тому же, эта оптимизация не относится к выводу типов.)]] | |
Функция copy()\скоп() используется для копирования тяжелых объектов, которое, как я считаю, должно быть обозначено явно в коде (просто через `оператор =` можно копировать только лёгкие объекты). | |
(Более подробное описание предлагаемого мной механизма работы с памятью — это тема для отдельной статьи, но вкратце скажу, что я предлагаю модель памяти несколько отличную[‘прежде всего отличие в принятых умолчаниях’] от C++ и Rust (но также без сборщика мусора)[[[, и более логичную, на мой взгляд]]].) | |
Задумывались ли вы над тем, что флаги открытия файла можно не указывать явно, а выводить из его использования? | |
#(11l)‘ | |
A fstr = File(fname).read() // А фстр = Файл(имя).прочти() | |
File(fname).write(contents) // Файл(имя).запиши(содержимое) | |
I File(fname) // заменяется компилятором на I fs:file_exists(fname) | |
File(fname).size // заменяется компилятором на fs:stat(fname).size | |
’ | |
[[[ | |
Если берётся часть строки, то результат имеет тип Строка\String или Подстрока\Substring в зависимости от использования. | |
Вот здесь можно использовать тип Подстрока\Substring для переменной `link`, так как переменная `link` не модифицируется: | |
#(11l)‘ | |
A link = instr[i + 1.<endb] | |
I A spacepos = link.find(‘ ’) | |
link = link[0.<spacepos] | |
I link.len > 57 | |
link = link[0..(link.rfind(‘/’, 47) ?? -1)+1]‘...’ | |
write_http_link(i, i, 0, linkn‘<i>’link‘</i>’)’(Этот код основан на ‘7 строках из pqmarkup.py’[https://bitbucket.org/pqmarkup/pqmarkup/src/0d0354719b0f9f02a7d746ca695e32981b7e8e95/pqmarkup.py#pqmarkup.py-302].) | |
А если где-то в коде изменяется значение переменной `link`, то её тип устанавливается компилятором как Строка\String. | |
Также обратите внимание, что использование типа Подстрока\Substring в данном примере возможно благодаря тому, что `instr` — константный аргумент функции (а по умолчанию все аргументы функции целесообразно считать константными). | |
Признак константности также можно выводить из использования — если переменная нигде не изменяется, то к её типу компилятор может автоматически добавить спецификатор константности, после чего компилятор может полагаться на это. | |
]]] | |
Н‘Единая/одна конфигурация сборки’ | |
Сталкивались ли вы с проблемой отладки Release/Optimized сборок? Или с различным поведением в Debug и Release/Optimized? Возможно, проблемы от такого разделения возникают не так уж часто, но когда/если они возникают, это становится большой головной болью для программистов. Я считаю, что стоит попробовать избавить программистов-пользователей языка от такой боли путём отказа от этого разделения, добавив полноценную отладку в Release/Optimized build. К примеру, взять gdb, он ‘выдаёт optimized out’[https://stackoverflow.com/questions/5497855/what-does-value-optimized-out-mean-in-gdb ‘But here the a is not redundant,it needs to be used later’] в случае когда переменная не хранится в стеке. Но если переменная используется, то где-то же (в регистре или где-либо в памяти) она хранится! Почему в отладочной информации это не предусмотрели — для меня загадка. Ведь посмотреть значение регистров можно на каждом шаге отладки, а значения переменных, хранящихся в регистрах, уже нельзя. | |
Теоретически, это предложение возможно реализовать и в существующих компиляторах, но, предполагаю, что это слишком сложно, так как это не закладывалось в существующие компиляторы в начале их разработки. | |
Н‘Явное обозначение области видимости переменных при обращении к ним (синтаксическая соль)’ | |
Глобальные переменные я предлагаю обозначать префиксом `:` (смотри `:numbers` в примере выше). | |
Переменные объектов — префиксом `.` (так, например, `.x` означает обращение к переменной[/члену] объекта с именем `x`), переменные типов (или статические\static в терминологии C++) — префиксом `.:`. | |
Префикс @ (символ похож на [[[символы]]]объединение двух букв C и a — *‘C’*‘a’pture\*‘С’хв*‘а’тить) — для захвата внешних переменных внутри [[[лямбда-]]]локальных функций, аналог nonlocal из Python. | |
Префикс `^` — для доступа к переменным из внешней ‘области видимости’\scope, это может быть полезно во время отладки (например, есть цикл по `i`, внутри него ещё какой-то цикл, внутри которого ещё маленький цикл по `i`, находясь в котором хочется получить текущее значение переменной `i` верхнего уровня, это можно сделать посредством записи `^i`). Также ^ можно использовать для возврата из внешней функции внутри [[[лямбда-]]]локальной функции: | |
#(11l)‘ | |
F outer_func(...) | |
F local_func(...) | |
^R // (or ^(outer_func)R) return from outer_func | |
’ | |
В данной статье я коротко описал наиболее интересные черты нового языка (а также некоторые возможности, которые могли бы перенять уже существующие языки). Если кого-либо заинтересовал данный проект, напишите мне личное сообщение.[[[ Для [[[доступа к сайту]]]регистрации на форуме проекта необходимо получить приглашение (путём сообщения сведений о себе в личном сообщении через Хабр). Также приглашение можно получить если привлечь кого-то другого (допустим, вы знаете более опытного человека, который мог бы заинтересоваться данным проектом [и который мог бы конструктивно участвовать в обсуждении проекта, либо непосредственно принимать участие в написании документации или даже в реализации языка], вы даёте ему ссылку на эту статью, он отправляет [в личном сообщении] информацию о себе с указанием вашего имени пользователя и помимо него, приглашение получаете и вы). Такая [простая] схема/система нужна, во-первых, чтобы данная статья не являлась рекламой сайта проекта, во-вторых, для предварительного отсева случайных людей (другими словами, чтобы приглашение не получали все подряд), и, в-третьих, для стимуляции привлечения более опытных товарищей, которые редко читают Хабр и которые пропустили данную статью.]]] | |
‘P.S. В заключение приведу несколько примеров кода’{ | |
#(11l)‘ | |
T Person | |
String name | |
Int age | |
F (name, age) | |
.name = name // or (.).name = name, because (.) is this/self | |
.age = age | |
A persons = [] | |
persons [+]= Person("Name", 17) | |
// Translation of Python's `def parenthesize(s: Union[str, bytes]) -> Union[str, bytes]: ...` from [http://neopythonic.blogspot.com]: | |
F parenthesize(T C (String, Bytes) s) | |
... | |
// Doubly linked list | |
T DLListItem[T Type] | |
Type& prev // ‘unsafe pointer’/‘unowned reference’ [true weak pointer/reference `Type??` is much more expensive] | |
Type? next | |
F is_in_list // if method is defined without parentheses, than it must be called also without, i.e.: I it.is_in_list {...} | |
R next != N | |
T DLList[T Type(DLListItem)] // Type(DLListItem) means that Type must be derived from DLListItem | |
Type? first | |
Type& last | |
F.destructor // destructor is needed/necessary because some list item may be shared, and in that case it will not be removed from the list and also all following items will not be removed | |
.clear() | |
F clear() | |
Type? p = move(first) // this also sets `first` to N | |
L p | |
p.prev = N | |
Type? n = move(p.next) // this also sets `p.next` to N | |
p = n // move(n) is not needed here as the compiler put `move` automatically at all places of last use of variable | |
last = N | |
F append,[+]=(Type &item) // define both method `append()` and operator [+]= | |
item.prev = .last | |
I .last {.last.next = item} E .first = item | |
.last = item | |
F calc_len() | |
Int len = 0 | |
L (.) | |
len++ | |
R len | |
F L | |
F () -> Type& // returns iterator to the first element of this container | |
R .first | |
F next(it) -> Type& | |
R it.next | |
F prev(it) | |
R it.prev | |
Т ЛичныеДанные | |
Строка имя | |
Цел возраст | |
Ф (имя, возраст) | |
.имя = имя // или (.).имя = имя, так как (.) это this/self | |
.возраст = возраст | |
А массив_лд = [] | |
массив_лд [+]= ЛичныеДанные("Имя", 17) | |
// Перевод Python-кой записи `def parenthesize(s: Union[str, bytes]) -> Union[str, bytes]: ...` из [http://neopythonic.blogspot.com]: | |
Ф заключить_в_скобки(Т С (Строка, Байты) с) | |
... | |
// Двусвязный список | |
Т ЭлДССписка[Т Тип] | |
Тип& пред // небезопасная ссылка/указатель [честный слабый указатель `Тип??` значительно дороже] | |
Тип? след | |
Ф в_списке // если метод объявляется без скобок, то вызывать его необходимо также без скобок, например: Е эл.в_списке {...} | |
Р след != Н | |
Т ДССписок[Т Тип(ЭлДССписка)] // Тип(ЭлДССписка) означает, что Тип должен быть производным от ЭлДССписка | |
Тип? первый | |
Тип& последний | |
Ф.деструктор // деструктор нужен, так как какой-то элемент списка может быть shared\разделён, и в этом случае он не будет удалён из списка также как и последующие элементы | |
.очистить() | |
Ф очистить() | |
Тип? п = перем(первый) // это также сбрасывает `первый` в Н | |
Ц п | |
п.пред = Н | |
Тип? с = перем(п.след) // это также сбрасывает `п.след` в Н | |
п = с // перем(с) здесь не обязательно, так как компилятор вставляет `перем` автоматически в места последнего использования переменной | |
последний = Н | |
Ф добавить,[+]=(Тип &эл) // объявляем как метод `append()`, так и оператор [+]= (может быть полезно для "локализованных" исходных файлов: F append,добавить ...) | |
эл.пред = .последний | |
Е .последний {.последний.след = эл} И .первый = эл | |
.последний = эл | |
Ф вычисл_длину() | |
Цел длина = 0 | |
Ц (.) | |
длина++ | |
Р длина | |
Ф Ц | |
Ф () -> Тип& // возвращает итератор контейнера (в данном случае — указатель на первый элемент списка) | |
Р .первый | |
Ф след(эл) -> Тип& | |
Р эл.след | |
Ф пред(эл) | |
Р эл.пред | |
’ | |
} | |
[[[ | |
Опрос: | |
Какое название вы бы дали предлагаемому языку программирования? | |
. 11a | |
. 11c (c - characters) | |
. 11l (l — litterae) | |
. 11s (s - symbols) | |
. другое название | |
. я против создания подобного языка программирования | |
]]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment