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():
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() в случаях:Person p
p.name = ...
p.age = ...
persons.append(p) // в С++ здесь пришлось бы писать persons.push_back(std::move(p)) для оптимальности
А также позволит избежать копирования при вызове функции sorted()\сортй():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 являются массивами):
a2 = a1;
В языке C++ в этом случае будет выполнено поэлементное копирование массива. А в языке Python эта же строка сделает 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 пользователей.