Кедр это универсальный язык программирования, предназначенный как для пользователей с минимальной подготовкой, так и для разработки операционных систем, драйверов устройств и игр.

Система типов

Программист может объявить типы следующих видов:

  • объект

  • структура

  • объединение

  • именованный кортеж

  • символ

  • тип-сумма

  • флаги

Объект

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)


  1. nikkadim
    24.05.2024 12:35
    +2

    похож на F shap


  1. Vitimbo
    24.05.2024 12:35
    +1

    Не понимаю, как язык будет решать поставленные задачи лучше, чем тот же Си, в который он транслируется.


    1. RichardMerlock
      24.05.2024 12:35
      +47

      По-хорошему, Кедру нужен промежуточный Си-подобный язык Сидр, но главное, чтоб не модный интерпретатор.


      1. tmxx
        24.05.2024 12:35
        +4

        полагаю, из-за названия модного Питон-подобного интерпретатора?


    1. domix32
      24.05.2024 12:35

      Подозреваю подразумевается строгая проверка типов и поддержка форсирования некоторых поведений (по аналогии с ржавым borrowc). Ну и более приятная система типов лучше чем `(*(whatever*)(void*)(const char*)data)->callme();`. Система наследования конечно под вопросом, но это уже тема о фломастерах.


      1. denismarkelov Автор
        24.05.2024 12:35

        Интересно подробнее послушать о вопросах по системе наследования.


        1. 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() -> ...
          }


          1. denismarkelov Автор
            24.05.2024 12:35
            +1

            Есть типаж как в Rust, которым можно ограничить переменную типа. Есть интерфейс как в CSharp, к которому можно привести объект. Наследования с созданием поля как в Jai/Odin я не рассматривал. Отдельного синтаксиса для реализации интерфейса нет, нужно его унаследовать, после чего определить методы.


            1. domix32
              24.05.2024 12:35

              Перегузки/дефолтные имплементации будут?


              1. denismarkelov Автор
                24.05.2024 12:35

                Здесь ещё не принято решение. Код проще если определенный метод нельзя переопределить, так реализовано сейчас. Но если это вызовет сложности - можно разрешить, если у метода есть соответствующий атрибут.


    1. denismarkelov Автор
      24.05.2024 12:35

      За счет наличия абстракций более высокого уровня. С минималистичен, хороший промежуточный язык, но вручную писать на нём код сложно.


      1. leahch
        24.05.2024 12:35
        +3

        Хочется спросить - чем сложно? Почему не F/C++/CS/Java/Kotlin и сотня других? Как минимум, они все обосновывают концепцию, почему и зачем еще один самокат...


        1. gmtd
          24.05.2024 12:35

          Автор же написал - чтобы сделать ультимативно универсальный язык


    1. Ydav359
      24.05.2024 12:35

      Писать код однозначно будет проще с дженериками, нормальными строками и т.д.


  1. domix32
    24.05.2024 12:35
    +1

    А зеркало будет? Что с лицензированием? Где смотреть спеку по языку/стандартной библиотеке? Где тесты? Что по примерам интероперабельности с теми же сями? Что там по CI/CD stable/dev/nightly?


    1. denismarkelov Автор
      24.05.2024 12:35

      О зеркалах не думал. Вероятно будет общественное достояние, не вижу пользы от лицензий. Документации пока нет. По взаимодействию с С вскоре будет статья. CI рано.


  1. Tttttttgg
    24.05.2024 12:35

    Спасибо , интересно. Учу ребенка 7 лет программированию. Есть что нибудь на русском, кроме дракона. И с чего начать чтобы максимально близко к питону подойти


    1. alexxisr
      24.05.2024 12:35

      а почему не сразу с питона? английский сейчас вроде с первого класса учат - будет мотивация и практика.


      1. Tttttttgg
        24.05.2024 12:35

        7 лет, пока с трудом сктретч идёт. Хотя может просто время нужно. Спасибо)


        1. 1755
          24.05.2024 12:35
          +5

          А ребёнок сам хочет учить программирование?)


          1. Tttttttgg
            24.05.2024 12:35
            +1

            Он не знает) компы нравятся хоть просто ч о то лёгкое дать чтоб был опыт который пи желании потом можно будет вспомнить


    1. DungeonLords
      24.05.2024 12:35
      +1

      Игра для тех, кто учится программировать: Colobot


    1. OnGrey
      24.05.2024 12:35

      До 11 лет (-/+ 1 год) только визуальное программирование. Раньше мозг ребенка ещё не готов соотносить слова с реальной реализацией, есть уникумы конечно… но это обычно с очень переразвитым математическим аппаратом. В вашем случае только визуальное (тот же скретч на реализацию визуальных действий - передвижений объекта, разделения и т.п.) с упором на решение алгоритмических задач визуально. + Можно научить правилам шахмат и решать шахматные задачи на логику. В виде доп задания на платформах обучения это уже 1 классе дают.


  1. sci_nov
    24.05.2024 12:35

    А почему не сосна? )


    1. Tttttttgg
      24.05.2024 12:35
      +1

      Кедр это сосна) это Петр первый велел кедр называть сосну сибирскую кедром


      1. blik13
        24.05.2024 12:35

        Даже тот кедр, который кедр, немножко сосна)


        1. sci_nov
          24.05.2024 12:35
          +1

          Не видел, не знаю. Но кедр у Сибири отличается от сосны.


          1. qrKot
            24.05.2024 12:35
            +2

            кедр, который кедр - это ливанский кедр, истребленный в свое время в целях постройки кораблей. Современный кедр, который произрастает в Сибири - это кедровая сосна. Род сосен включает в себя более 100 видов, причем "сосна кедровая" действительно отличается от "сосны обыкновенной".


            1. sci_nov
              24.05.2024 12:35

              Да, что-то в школе проходили про это


      1. sci_nov
        24.05.2024 12:35

        Правильно сделал)


  1. leahch
    24.05.2024 12:35
    +5

    Прекрасная вещь, еще одни грабли, или велосипед. Неясно, что и как конкретно из типичных проблем других языков решает этот?

    Вот, тольок сегодня прочитал про прекрасный язык rocket lang. И думаю, именно за этой концепцией будущее - язык программирования для DSL.


    1. ibnteo
      24.05.2024 12:35
      +1

      Развитые языки с DSL: Elixir, Kotlin, Nim.


  1. leahch
    24.05.2024 12:35

    И зачем типы? Почему не просто функции и данные, ну или записи? Как в clojure например?


  1. Panzerschrek
    24.05.2024 12:35
    +1

    Выглядит любопытно.

    Хотелось бы прояснить ряд технических моментов. На каком языке написан компилятор? Используется ли LLVM?


    1. denismarkelov Автор
      24.05.2024 12:35

      LLVM не используется, это большая зависимость, промежуточный язык С предпочтительнее. Компилятор написан на Kotlin.


  1. sci_nov
    24.05.2024 12:35
    +1

    Надо было назвать πdr: пи-по-дээр, вполне математично)


  1. nav68
    24.05.2024 12:35

    Коль уж "кедр" - так и язык русский. А то опять русско-вражеское получается. Хотел, как лучше, а сделал -как всегда...


    1. sci_nov
      24.05.2024 12:35
      +1

      Что значит вражеское? Это общепринятая международная практика программировать на английском. Неустранимая я бы сказал.

      Но да, название - на любителя.


  1. ManGegenMann
    24.05.2024 12:35
    +1

    По низам фонит?


  1. kotlomoy
    24.05.2024 12:35
    +2

    А почему свой редактор, а не расширение к VSCode или др. редакторам/IDE?

    Блоки кода будут индентацией определяться? А как будет работать автоформатирование при рефакторинге с переносом больших кусков кода?


    1. denismarkelov Автор
      24.05.2024 12:35

      Редактор это большое поле для новых идей, и реализовать их можно только в условиях полной свободы.

      Да, структура кода задаётся отступами. Всё что нужно после вставки большого фрагмента кода - установить ему нужный отступ.


  1. 1Tiger1
    24.05.2024 12:35
    +5

    давайте я расскажу как правильно:

    какие задачи решает язык?

    по каким параметрам этот язык лучше чем язык Х.

    где скачать компилятор/интерпретатор языка, а лучше сразу онлайн песочница.

    вот смотрите бенчмарк тесты на таких то и таких задачах.

    вот тут репозиторий (если это опенсорс) а вот тут формируется сообщество (если нет), все сюда!

    а вот тут репозиторий проекта (достаточно крупного чтобы оценить удобство) на этом языке.

    хотя бы так. если вы стартуете с нуля. вообще для его принятия рынком потребуется намного больше.

    P. S. если ваш язык универсален - скорее всего он бесполезен. исключений немного, и все они или разрабатывались или крупными компаниями и экспертам с огромными опытом, или прошли десятки лет развития в опенсорс. сфокусируйтесь на чем то, на какой то области, чем уже тем лучше, и закройте все её потребности в чем то лучше чем мейнстрим язык в этой области. иначе никому нет смысла его принимать для реального проекта. новый язык это ОЧЕНЬ дорого. после успеха (если будет) можете выпустить новый, на базе этого но более универсальный , с учётом полученного опыта и с ресурсами на его распространение и создание необходимой инфраструктуры (фреймворк и прочие) . под ресурсами тут имеется ввиду не столько деньги сколько репутация и возможность запартнерить других людей и компании на создание инфраструктуры и использование в своих проектах.

    хотя если это просто фан проект то не обязательно. но тогда не расстраивайтесь если он никому не будет нужен. потому что люди не используют новые языки просто так, потому что захотелось или потому что у него есть фишки работы с пямятью. просто представьте себя на месте техдира который решит его использовать для продукта. новый язык это как минимум обучение ему с нуля всех новых разработчиков. 4-6 зарплат на каждого вынь да положь. и так при любой смене разработчика. и это не самая большая проблема. даже не в первой десятке. просто самая наглядная.