geektimes.ru/post/100171:
I'm always delighted by the light touch and stillness of early programming languages. Not much text; a lot gets done. Old programs read like quiet conversations between a well-spoken researcher and a well-studied mechanical colleague, not as a debate with a compiler. Who'd have guessed sophistication bought such noise?
– Dick Gabriel
...
The standard languages (Java, C++) are statically typed.
Most outsider languages (Ruby, Python, JavaScript) are interpreted and dynamically typed.
Perhaps as a result, non-expert programmers have confused "ease of use" with interpretation and dynamic typing.
The world has split into a false dichotomy: nice, interpreted, dynamic versus ugly, compiled, static.
Time to put things right.Меня всегда восхищала лёгкость и спокойствие ранних языков программирования. Немного текста; но делается многое. Старые программы читаются как тихие разговоры между хорошо говорящим исследователем и хорошо обученным механическим коллегой, а не как дебаты с компилятором. Кто бы мог подумать что изощрённость принесёт так много шума?
— Дик Габриел
...
Стандартные языки (Java, C++) — статически типизированные.
Большинство языков-аутсайдеров (Ruby, Python, JavaScript) — интерпретируемые и динамически типизированные.
Возможно в результате этого программисты неспециалисты путают "простоту использования" с интерпретируемостью и динамической типизацией.
Мир разделился ложной дихотомией: приятные, интерпретируемые, динамические против уродливых, компилируемых, статических.
Пришло время чтобы исправить эту ситуацию.
На этой пафосной ноте перейду к примерам на новом языке программирования.
Вот маленький пример кода:
(Попробуйте угадать значения зарезервированных букв, а также мысленно переписать этот кусочек кода на языках программирования, которые вы знаете.)А вот пример побольше (это перевод Python-кода из моей предыдущей статьи).
Код, использующий [зарезервированные] буквы
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.last == ns[1] & digest2[0] == ns[0] & digest2.last == ns[1] results [+]= ns // Берём первую половину хэша digest1 = digest1[0 .< (len) I/ 2] digest2 = digest2[0 .< (len) I/ 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())
Код, использующий [зарезервированные] слова
Как можно заметить, импорт модулей осуществляется неявно — можно просто обращаться к нужной функции пиша
Для сырых строк можно использовать одиночные парные кавычки — на мой взгляд строки в таких кавычках выглядят понятнее. [Ещё варианты для сырых строк:
Согласен, что этот пример fn calculate_sacred_number() var results = [] loop(hash_algorithm) hashlib:algorithms_available // Обходим все доступные хэш-алгоритмы \\ (список включает в себя MD5, SHA...) if.unlikely "shake" in hash_algorithm // Пропускаем алгоритмы SHAKE, так как ... loop.continue loop(uppercase) 0B..1B // Проверяем варианты написания как строчными, так и ПРОПИСНЫМИ loop(space) 0B..1B // Проверяем варианты написания с дефисом и через пробел loop(n) 10..99 // Проверяем все двузначные числа var nw = :numbers[n] // Получаем текущее число, записанное словами на англ. if uppercase nw .= upper() if space nw .= replace(‘-’, ‘ ’) var ns = String(n) // Считаем хэш от записанного словами числа, var digest1 = hashlib:(hash_algorithm, nw.encode()).hexdigest() // а также от этого же числа, преобразованного в строку. var digest2 = hashlib:(hash_algorithm, ns.encode()).hexdigest() loop 2 // Проверяем целый хэш, а также первую половину хэша // Оба хэша должны начинаться на первую цифру текущего числа // и заканчиваться на вторую цифру. if digest1[0] == ns[0] & digest1.last == ns[1] & digest2[0] == ns[0] & digest2.last == ns[1] results [+]= ns // Берём первую половину хэша digest1 = digest1[0 .< (len) I/ 2] digest2 = digest2[0 .< (len) I/ 2] assert(results.len == 1) // Должно быть только одно "выигравшее" число return results[0] // Возвращаем это число // Based on [https://stackoverflow.com/a/8982279/2692494 ‘How do I tell Python to convert integers into words’] var 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() loop(tens) "twenty thirty forty fifty sixty seventy eighty ninety".split() loop(ones) numbers[0..9] numbers [+]= if ones == "zero" {tens} else 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).Для сырых строк можно использовать одиночные парные кавычки — на мой взгляд строки в таких кавычках выглядят понятнее. [Ещё варианты для сырых строк:
'"строка"', ''""строка с "'""'', `строка`.]"http://...?rev=" + revision).
Язык поддерживает всего 11/12/13 базовых зарезервированных букв/слов:
(Исходные файлы на языке должны быть оформлены единообразно — либо используя зарезервированные слова, либо используя буквы.)
Данный/предлагаемый язык дополнительно отличается от других тем, что его зарезервированные слова идут не просто в виде списка (как принято в[о всех[?]] других языках программирования), а структурированы в иерархию, на верхнем уровне которой располагаются 11/12/13 корневых/базовых зарезервированных букв/слов.
| Буквы | Слова | Пояснение | ||
|---|---|---|---|---|
| A | var | перем | объявление переменной автоматического типа (аналог 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, decltype в C++11) | |
| X | Э | extra | экстра | для дополнительных зарезервированных подслов |
Данный/предлагаемый язык дополнительно отличается от других тем, что его зарезервированные слова идут не просто в виде списка (как принято в[о всех[?]] других языках программирования), а структурированы в иерархию, на верхнем уровне которой располагаются 11/12/13 корневых/базовых зарезервированных букв/слов.
Примеры зарезервированных подслов:
| I\Е | I.likely\Е.часто I.unlikely\Е.редко |
| Е\И | E.try E.throw E.catch\И.перехват E.try_end (аналог try-else в Python) |
| F\Ф | F.args\Ф.арг F.virtual.new\Ф.виртуал.новая F.virtual.override\Ф.виртуал.переопр F.virtual.final\Ф.виртуал.финал F.virtual.abstract\Ф.виртуал.абстракт F.destructor\Ф.деструктор |
| L\Ц | L.continue\Ц.продолжить L.on_continue\Ц.при_продолжении L.break\Ц.прервать L.on_break\Ц.при_прерывании L.again — перейти к началу цикла L.was_no_break\Ц.не_был_прерван L.index — номер текущей итерации цикла (начиная с 0) L.next\Ц.след — следующее значение переменной цикла L.prev\Ц.пред — предыдущее значение переменной цикла |
| S | S.break S.fallthrough |
| T | T.base\Т.базовый — для доступа к базовому типу T.enum\Т.перечисл — объявление перечисления |
Два вида деструкторов
- Вызывается при выходе из области видимости (как обычный деструктор в С++ и других языках с поддержкой RAII) —
F.on_scope_exit(также эту запись можно использовать как аналог defer в Go и scope(exit) в D). - Объект разрушается сразу после последнего использования/обращения —
F.destructor.
Основным я предлагаю сделать второй вариант.
Это позволит удалять временный файл сразу после окончания работы с ним без явного вызова close():
А также избавит от необходимости писать move() в случаях: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)
А также позволит избежать копирования при вызове функции sorted()\сортй():Person p p.name = ... p.age = ... persons.append(p) // в С++ здесь пришлось бы писать persons.push_back(std::move(p)) для оптимальности
for root, dirs, files in os.walk(path): for file in sorted(files): # в C++ бы пришлось писать что-то вроде (files.sort(), files) для избежания копирования массива files ...
Развивая идею вывода типов (type inference)
Для начала скажу про такой момент, что в предлагаемом языке есть такие служебные функции как copy()\скоп() и share()\разд().
Тип умного указателя я предлагаю определять по его использованию (также как тип массива или словаря в Nemerle определяется по типу первого добавленного в него элемента). Получается эдакий autounishared_ptr — гибрид unique_ptr и shared_ptr из C++11. Если где либо в коде встречается share(p), тогда p становится shared_ptr-ом, иначе остаётся unique_ptr-ом.
Функция copy()\скоп() используется для копирования тяжелых объектов, которое, как я считаю, должно быть обозначено явно в коде (просто через
оператор = можно копировать только лёгкие объекты).Например, рассмотрим такую строчку кода (a1 и a2 являются массивами):
В языке C++ в этом случае будет выполнено поэлементное копирование массива. А в языке Python эта же строка сделаетa2 = a1;
a2 ссылкой на a1. Я предлагаю всегда писать явно: a2 = copy(a1) для копирования, и a2 = share(a1) для создания новой ссылки на массив.(Более подробное описание предлагаемого мной механизма работы с памятью — это тема для отдельной статьи, но вкратце скажу, что я предлагаю модель памяти несколько отличную от C++ и Rust (но также без сборщика мусора).)
Задумывались ли вы над тем, что флаги открытия файла можно не указывать явно, а выводить из его использования?
A fstr = File(fname).read() // А фстр = Файл(имя).прочитать() File(fname).write(contents) // Файл(имя).записать(содержимое) I File(fname) // заменяется компилятором на I fs:is_file(fname) File(fname).size // заменяется компилятором на fs:stat(fname).size
Единая/одна конфигурация сборки
Сталкивались ли вы с проблемой отладки Release/Optimized сборок? Или низкой производительностью Debug сборки? Или с различным поведением в Debug и Release/Optimized? Возможно, проблемы от такого разделения возникают не так уж часто, но когда/если они возникают, это становится большой головной болью для программистов. Я считаю, что стоит попробовать избавить программистов-пользователей языка от такой б��ли путём отказа от этого разделения, добавив полноценную отладку в Release/Optimized build. К примеру, взять gdb, он выдаёт optimized out в случае когда переменная не хранится в стеке. Но если переменная используется, то где-то же (в регистре или где-либо в памяти) она хранится! Почему в отладочной информации это не предусмотрели — для меня загадка. Ведь посмотреть значение регистров можно на каждом шаге отладки, а значения переменных, хранящихся в регистрах, уже нельзя.
Теоретически, это предложение возможно реализовать и в существующих компиляторах, но, предполагаю, что это слишком сложно, так как это не закладывалось в существующие компиляторы в начале их разработки.
Явное обозначение области видимости переменных при обращении к ним (синтаксическая соль)
Глобальные переменные я предлагаю обозначать префиксом
: (смотри :numbers в примере выше).Переменные объектов — префиксом
. (так, например, .x означает обращение к переменной[/члену] объекта с именем x), переменные типов (или статические\static в терминологии C++) — префиксом .:.Префикс @ (символ похож на объединение двух букв C и a — Capture\Схватить) — для захвата внешних переменных внутри локальных функций, аналог nonlocal из Python.
Префикс
^ — для доступа к переменным из внешней ‘области видимости’\scope, это может быть полезно во время отладки (например, есть цикл по i, внутри него ещё какой-то цикл, внутри которого ещё маленький цикл по i, находясь в котором хочется получить текущее значение переменной i верхнего уровня, это можно сделать посредством записи ^i). Также ^ можно использовать для возврата из внешней функции внутри локальной функции:F outer_func(...) F local_func(...) ^R // (or ^(outer_func)R) return from outer_func
В данной статье я коротко описал наиболее интересные черты нового языка (а также некоторые возможности, которые могли бы перенять уже существующие языки). Если кого-либо заинтересовал данный проект, напишите мне личное сообщение.
P.S. В заключение приведу ещё несколько примеров кода.
Ещё вот код по рисованию диаграммы, которая помогла мне определить оптимальное время для публикации данной статьи: сетевая часть (на Python и новом языке программирования) и сохранение файла изображения [диаграммы] (на C++ и новом языке программирования).
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\разделён, и в этом случае он не будет удалён из списка также как и последующие элементы .очистить() Ф очистить() Тип? п = перем(первый) // это также сбрасывает `первый` в Н Ц п п.пред = Н Тип? с = перем(п.след) // это также сбрасывает `п.след` в Н п = с // перем(с) здесь не обязательно, так как компилятор вставляет `перем` автоматически в места последнего использования переменной последний = Н Ф добавить, добавь, [+]=(Тип &эл) // также может быть полезно для "локализованных" исходных файлов: F append,добавить ... эл.пред = .последний Е .последний {.последний.след = эл} И .первый = эл .последний = эл Ф вычисл_длину() Цел длина = 0 Ц (.) длина++ Р длина Ф Ц Ф () -> Тип& // возвращает итератор контейнера (в данном случае — указатель на первый элемент списка) Р .первый Ф след(эл) -> Тип& Р эл.след Ф пред(эл) Р эл.пред
Ещё вот код по рисованию диаграммы, которая помогла мне определить оптимальное время для публикации данной статьи: сетевая часть (на Python и новом языке программирования) и сохранение файла изображения [диаграммы] (на C++ и новом языке программирования).
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какое название вы бы дали предлагаемому языку программирования?
0.7%11a1
2.1%11c (c — characters)3
0.7%11l (l — litterae)1
0.7%11s (s — symbols)1
2.8%другое название4
93.01%я против создания подобного языка программирования133
0%Дюжинакс (12 ключевых слов)0
0%13kw (13 keywords)0
0%13rw (13 reserved words)0
Проголосовали 143 пользователя. Воздержались 56 пользователей.

