Skip to content

Instantly share code, notes, and snippets.

@alextretyak
Last active March 6, 2018 00:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alextretyak/cb36e7f0e5f9b7d7a0700f051e148f4c to your computer and use it in GitHub Desktop.
Save alextretyak/cb36e7f0e5f9b7d7a0700f051e148f4c to your computer and use it in GitHub Desktop.
Заметки по новому языку программирования
[[[
[[[Хабы: Ненормальное программирование]]]
[[[Заголовок: [[[Заметки по новому языку программирования]/]]Каркас нового языка программирования]]]
[[[Метки: новый язык программирования, двойная статья]]]
]]][[[
Итак, как вы можете наблюдать, эволюция естественных языков завершилась[[[прежде всего потому, что нет [и не предвидится] достаточно ментально сильных людей, которые могли бы как-то повлиять на развитие языков]]] [с наступлением цифровой эпохи] (также как когда-то завершилась эволюция чисел).
Подходит к концу и эволюция языков программирования [общего назначения].
В качестве "своего" кандидата на ‘финальный[?]’[‘почему финальный — на мой взгляд энтропия[/хаотичность] (степень неупорядоченности) человеческой деятельности возрастает, и количество недостатков в новых языках [[[программирования]]] будет перевешивать количество преимуществ над предыдущими языками (с учётом затрат перехода на новые языки)’] "конкурс" языков программирования общего назначения я выдвигаю данный язык.
Сразу оговорюсь, что на данный момент язык существует лишь в форме/виде идеи и не слишком претендует на ‘практическую реализацию’[‘лично я осилил только лексический анализатор, да и тот, не уверен, что можно будет использовать практически/фактически’].
Но ]]][[[
Наверное, многие подумают, что начинать разработку нового языка программирования, тем более с нуля, независимым разработчиком без поддержки какой-либо корпорации[[[сейчас уже]]] не имеет смысла. Я же считаю, что [[[наступил/]]]сейчас вполне подходящий момент для анализа существующих языков программирования, а также их реализаций, и выделения тех особенностей/черт, которые сделали эти языки успешными.
Предлагаемый язык программирования не ставит собой задачу стать лучшим, а быть чуточку логичнее других, и просто достаточно хорошим, чтобы занять определённую нишу и найти своё место в этом, таком многогранном и противоречивом, мире.
]]][[[Представьте как здорово было бы иметь возможность писать код веб-сервера в стиле 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