Кедр это универсальный язык программирования, предназначенный как для пользователей с минимальной подготовкой, так и для разработки операционных систем, драйверов устройств и игр.
Система типов
Программист может объявить типы следующих видов:
объект
структура
объединение
именованный кортеж
символ
тип-сумма
флаги
Объект
type Buffer =
val type : BufferType
var object : VkBuffer
var size : u32
val offset : u64
val usage : BufferUsageFlags
У типа Buffer
пять полей, каждое из которых также является параметром конструктора.
type StorageBuffer =
inherit Buffer type = BufferType::Storage
offset = 0
let device : VkDevice
let memory : VkMemory
def discard =
vk::destroy_buffer device object null
vk::free_memory device memory null
Во время наследования мы передали значения полей type
и offset
, оставшиеся три поля становятся параметрами конструктора StorageBuffer
. К ним добавляются еще два параметра/поля device
и memory
. Видимость let
привязок и функций ограничена текущим файлом.
type List<T> @abstract =
...
type MutList<T> @[mut_of List] =
inherit List<T>
MutList
это изменяемая версия типа List
, доступен также под именем mut List
. List
является абстрактным, поэтому List.new
создает MutList
. new
используется для типов без параметров конструктора.
import collection::List@mrc
let list @owner = List.new
Модификатор @mrc
, расшифровывается как manual reference counting, дает доступ к версии типа без автоматического подсчета ссылок. Атрибут @owner
гарантирует освобождение памяти после выхода привязки из зоны видимости, также доступен для полей объектов.
По окончании файла тип привязки list
меняется с MutList
на List
(с атрибутом @mut
тип останется прежним). var
привязки без @mut
превращаются в val
.
type Array<T> @byval =
val size : u32
let ptr @param = kd_alloc (sizeof T * size) as MutPtr<T>
Объекты передаются по значению если задан атрибут @byval
. Код непосредственно внутри типа идёт в конструктор. Атрибут @param
позволяет передать значение привязки как именованный параметр конструктора, может также использоваться в функциях.
К типу можно добавлять поля и код конструктора до его наследования или создания первого объекта типа. Таким образом конструктор может быть распределен среди нескольких файлов.
Поддерживается множественное наследование, но каждый тип может быть унаследован только один раз.
Явное преобразование типов окончательно, из ссылки на объект типа List
нельзя снова получить MutList
.
Структура
type Vector3<T> = struct
x : T
y : T
z : T
let vector = Vector3 x = 1
y = 3
z = 8
let vector_ref = Vector3@ref 1
y = 3
..vector
Задавать значения полей можно позиционно, по имени и с помощью заполнителя. Тип заполнителя не имеет значения. Синтаксис работает одинаково для создания объектов, инициализации структур, наследования типов и вызова функций. Сначала идут позиционные аргументы, потом именованные, по одному на строку, и в конце заполнитель.
Структуру можно инициализировать в куче, если добавить к имени типа суффикс @ref
. Тип полученной привязки будет mut ref Vector3
, что также можно записать как MutRef<Vector3>
. Ссылки такого рода по сути являются указателями, которые вместо традиционных для указателей операций дают доступ к полям и методам типа.
type Vector3<T> where T : Real
def norm = x * x + y * y + z * z |> sqrt
def normalized = self / norm
def normalize @mut =
let l = norm
x /= l
y /= l
z /= l
В заголовке нет знака =
, значит это расширение уже существующего типа Vector3
. Оператор |>
передает левую часть в указанную справа функцию или вызывает метод. Метод normalize
имеет атрибут @mut
, что позволяет ему изменять значения полей структуры. Вызываться такой метод может только на модифицируемых структурах, т.е. на привязках let mut
и var
и значениях типа mut ref Vector3
.
Объединение
type ClearColorValue = union
float_32 : [f32; 4]
int_32 : [i32; 4]
uint_32 : [u32; 4]
Все поля указывают на один адрес в памяти. При создании нужно задать значение одного поля.
Именованный кортеж
type VkInstance = MutPtr
type CString = Ptr<char>
type X = i32
type Some<T> = T
type CString ptr
fun get (i : u32) = ptr[i]
Может иметь любое количество элементов, но чаще всего элемент один, так как иначе структура часто будет лучшим выбором.
Особые функции задаются через ключевое слово fun
, fun get
это индексатор.
Символ
Также известен как единичный тип. По сути является именованным кортежем с нулём элементов.
type None = symbol
Используется в типах-суммах.
Тип-сумма
Отличается от объединения наличием информации об активном поле.
type BufferType =
| Vertex
| Index
| Storage
| Image
| Uniform
Перечисление это тип-сумма единичных типов.
type Option<T> =
| None
| Some T
type Result<T, E> =
| Ok T
| Error E
Option
позволяет представить отсутствие значения. Функция возвращает Result
чтобы передать информацию об ошибке в случае неудачи.
type Metal @open = enum
Iron | Copper | Nickel
type Metal
Silver | Gold
type Platinum = symbol
inherit Metal
К открытым типам-суммам поля можно добавлять в любой точке кода. Модификатор enum
ограничивает типы полей единичными.
type Target =
| Buffer VkBuffer
| Image image : VkImage
width : u32
height : u32
Символы, именованные кортежи и структуры, определенные внутри типа-суммы, могут быть использованы отдельно как полноценные типы.
let value = Some 5
let metal = Metal::Silver
Привязки value
и metal
имеют типы Some<i32>
и Silver
, вместо Option<i32>
и Metal
, как можно было бы предположить.
Флаги
type QueueFlags = flags
| Graphics
| Compute
| Transfer
| SparseBinding
| Protected
let queue_flags = QueueFlags::Graphics | QueueFlags::Transfer
assert queue_flags.contains QueueFlags::Graphics
Упорядоченный код
Код может ссылаться только на элементы, которые определены выше (исключение — идущие подряд функции видят друг друга). Таким образом устранена несогласованность, когда код внутри функций последователен, а снаружи нет. Также не нужна входная точка в приложение, весь код верхнего уровня выполняется после запуска. Элементы можно импортировать для модуля, а не отдельно для каждого файла. Но главное следствие — такой код можно прочитать от начала до конца, с ним легко работать.
Цель языка
Мы хотели бы иметь один язык, оптимальный для каждой из задач программирования. Вопрос освобождения памяти нас в этом ограничивает — одно приложение требует ручного управления, другое может воспользоваться всеми преимуществами сборщика мусора. Здесь нужен компромисс.
В остальных вопросах компромисс не нужен, язык должен быть настолько хорош, насколько позволяет наше текущее понимание математики вычислений, чтобы любая задача на нем решалась так, как будто язык был специально для неё создан.
Ход разработки
Есть компилятор в С и редактор кода, которые я переписываю на Кедр, после чего выйдет пробная версия. Здесь можно следить за процессом.
Комментарии (42)
Vitimbo
24.05.2024 12:35+1Не понимаю, как язык будет решать поставленные задачи лучше, чем тот же Си, в который он транслируется.
RichardMerlock
24.05.2024 12:35+47По-хорошему, Кедру нужен промежуточный Си-подобный язык Сидр, но главное, чтоб не модный интерпретатор.
domix32
24.05.2024 12:35Подозреваю подразумевается строгая проверка типов и поддержка форсирования некоторых поведений (по аналогии с ржавым borrowc). Ну и более приятная система типов лучше чем `(*(whatever*)(void*)(const char*)data)->callme();`. Система наследования конечно под вопросом, но это уже тема о фломастерах.
denismarkelov Автор
24.05.2024 12:35Интересно подробнее послушать о вопросах по системе наследования.
domix32
24.05.2024 12:35Пока не совсем понятно как именно оно устроено. Также как в плюсах - тащит vtable с функциями и их перегрузками, данными (членами класса) и деструкторами, или оно как композиция интерфейсов - аналогчно трейтам в Rust, протоколам в Swift и интерфесам в C sharp. Будет ли наследование субтипов по аналогии с Jai/Odin?
Entity :: struct { using position: Vector3, // this orientation: quaternion128, } Colour :: struct {r, g, b, a: u8} Frog :: struct { ribbit_volume: f32, using entity: Entity, // this colour: Colour, } frog.x = 123 // this
Или как Swift/Kotlin будут поддержаны оба три варианта через разный синтаксис
struct A : B { // отнаследовать поля и методы fn blabla() { ... } } struct A extends Iterator { // поддержать интерфейс fn next() -> ... }
denismarkelov Автор
24.05.2024 12:35+1Есть типаж как в Rust, которым можно ограничить переменную типа. Есть интерфейс как в CSharp, к которому можно привести объект. Наследования с созданием поля как в Jai/Odin я не рассматривал. Отдельного синтаксиса для реализации интерфейса нет, нужно его унаследовать, после чего определить методы.
domix32
24.05.2024 12:35Перегузки/дефолтные имплементации будут?
denismarkelov Автор
24.05.2024 12:35Здесь ещё не принято решение. Код проще если определенный метод нельзя переопределить, так реализовано сейчас. Но если это вызовет сложности - можно разрешить, если у метода есть соответствующий атрибут.
denismarkelov Автор
24.05.2024 12:35За счет наличия абстракций более высокого уровня. С минималистичен, хороший промежуточный язык, но вручную писать на нём код сложно.
domix32
24.05.2024 12:35+1А зеркало будет? Что с лицензированием? Где смотреть спеку по языку/стандартной библиотеке? Где тесты? Что по примерам интероперабельности с теми же сями? Что там по CI/CD stable/dev/nightly?
denismarkelov Автор
24.05.2024 12:35О зеркалах не думал. Вероятно будет общественное достояние, не вижу пользы от лицензий. Документации пока нет. По взаимодействию с С вскоре будет статья. CI рано.
Tttttttgg
24.05.2024 12:35Спасибо , интересно. Учу ребенка 7 лет программированию. Есть что нибудь на русском, кроме дракона. И с чего начать чтобы максимально близко к питону подойти
OnGrey
24.05.2024 12:35До 11 лет (-/+ 1 год) только визуальное программирование. Раньше мозг ребенка ещё не готов соотносить слова с реальной реализацией, есть уникумы конечно… но это обычно с очень переразвитым математическим аппаратом. В вашем случае только визуальное (тот же скретч на реализацию визуальных действий - передвижений объекта, разделения и т.п.) с упором на решение алгоритмических задач визуально. + Можно научить правилам шахмат и решать шахматные задачи на логику. В виде доп задания на платформах обучения это уже 1 классе дают.
sci_nov
24.05.2024 12:35А почему не сосна? )
Tttttttgg
24.05.2024 12:35+1Кедр это сосна) это Петр первый велел кедр называть сосну сибирскую кедром
blik13
24.05.2024 12:35Даже тот кедр, который кедр, немножко сосна)
sci_nov
24.05.2024 12:35+1Не видел, не знаю. Но кедр у Сибири отличается от сосны.
qrKot
24.05.2024 12:35+2кедр, который кедр - это ливанский кедр, истребленный в свое время в целях постройки кораблей. Современный кедр, который произрастает в Сибири - это кедровая сосна. Род сосен включает в себя более 100 видов, причем "сосна кедровая" действительно отличается от "сосны обыкновенной".
leahch
24.05.2024 12:35+5Прекрасная вещь, еще одни грабли, или велосипед. Неясно, что и как конкретно из типичных проблем других языков решает этот?
Вот, тольок сегодня прочитал про прекрасный язык rocket lang. И думаю, именно за этой концепцией будущее - язык программирования для DSL.
leahch
24.05.2024 12:35И зачем типы? Почему не просто функции и данные, ну или записи? Как в clojure например?
Panzerschrek
24.05.2024 12:35+1Выглядит любопытно.
Хотелось бы прояснить ряд технических моментов. На каком языке написан компилятор? Используется ли LLVM?denismarkelov Автор
24.05.2024 12:35LLVM не используется, это большая зависимость, промежуточный язык С предпочтительнее. Компилятор написан на Kotlin.
nav68
24.05.2024 12:35Коль уж "кедр" - так и язык русский. А то опять русско-вражеское получается. Хотел, как лучше, а сделал -как всегда...
sci_nov
24.05.2024 12:35+1Что значит вражеское? Это общепринятая международная практика программировать на английском. Неустранимая я бы сказал.
Но да, название - на любителя.
kotlomoy
24.05.2024 12:35+2А почему свой редактор, а не расширение к VSCode или др. редакторам/IDE?
Блоки кода будут индентацией определяться? А как будет работать автоформатирование при рефакторинге с переносом больших кусков кода?
denismarkelov Автор
24.05.2024 12:35Редактор это большое поле для новых идей, и реализовать их можно только в условиях полной свободы.
Да, структура кода задаётся отступами. Всё что нужно после вставки большого фрагмента кода - установить ему нужный отступ.
1Tiger1
24.05.2024 12:35+5давайте я расскажу как правильно:
какие задачи решает язык?
по каким параметрам этот язык лучше чем язык Х.
где скачать компилятор/интерпретатор языка, а лучше сразу онлайн песочница.
вот смотрите бенчмарк тесты на таких то и таких задачах.
вот тут репозиторий (если это опенсорс) а вот тут формируется сообщество (если нет), все сюда!
а вот тут репозиторий проекта (достаточно крупного чтобы оценить удобство) на этом языке.
хотя бы так. если вы стартуете с нуля. вообще для его принятия рынком потребуется намного больше.
P. S. если ваш язык универсален - скорее всего он бесполезен. исключений немного, и все они или разрабатывались или крупными компаниями и экспертам с огромными опытом, или прошли десятки лет развития в опенсорс. сфокусируйтесь на чем то, на какой то области, чем уже тем лучше, и закройте все её потребности в чем то лучше чем мейнстрим язык в этой области. иначе никому нет смысла его принимать для реального проекта. новый язык это ОЧЕНЬ дорого. после успеха (если будет) можете выпустить новый, на базе этого но более универсальный , с учётом полученного опыта и с ресурсами на его распространение и создание необходимой инфраструктуры (фреймворк и прочие) . под ресурсами тут имеется ввиду не столько деньги сколько репутация и возможность запартнерить других людей и компании на создание инфраструктуры и использование в своих проектах.
хотя если это просто фан проект то не обязательно. но тогда не расстраивайтесь если он никому не будет нужен. потому что люди не используют новые языки просто так, потому что захотелось или потому что у него есть фишки работы с пямятью. просто представьте себя на месте техдира который решит его использовать для продукта. новый язык это как минимум обучение ему с нуля всех новых разработчиков. 4-6 зарплат на каждого вынь да положь. и так при любой смене разработчика. и это не самая большая проблема. даже не в первой десятке. просто самая наглядная.
nikkadim
похож на F shap