Я делаю разный софт на Go, Python и C++
0,0
рейтинг
13 июля 2015 в 17:15

Разработка → Высокоуровневый С или пару слов о Cello

imageCello — это библиотека, которая сделала высокоуровневый C возможным! Обобщения (generics), параметрический полиморфизм, интерфейсы, конструкторы/деструкторы, сборщик мусора (по желанию), исключения и рефлекция. Да-да, ты не ослышался, все эти плюхи в одном флаконе. Так как Cello построен в пределах стандарта С, в сухом остатке ты получишь все, что нужно живому человеку на земле: высокую производительность, мощный инструментарий и гибкие библиотеки.

Talk is cheap, show me the code!

#include "Cello.h"

int main(int argc, char** argv) {

  /* Stack objects are created using "$" */
  var i0 = $(Int, 5);
  var i2 = $(Int, 3);
  var i2 = $(Int, 4);

  /* Heap objects are created using "new" */
  var items = new(Array, Int, i0, i1, i2);

  /* Collections can be looped over */
  foreach (item in items) {
    print("Object %$ is of type %$\n",
      item, type_of(item));
  }

  /* Heap objects destructed via Garbage Collection */
  return 0;
}

ШОК! Зачем же мне теперь все эти ваши Go/D/Nim/<впиши>, если С на стероидах решает все проблемы рода человеческого?! Хочешь узнать о готовности Cello к продакшну и увидеть еще больше кода? Добро пожаловать подкат.

Вводная


Cello добавляет поверх С дополнительный слой рантайма. Это абсолютная необходимость, потому что иначе расширить язык было бы возможно только меняя компилятор, а такую роскошь мы себе позволить не можем. Пользователь определяет типопеременные (runtime type variables), которые содержат всю необходимую информацию для нового функционала, связывая их с обычными легитимными типами.

Оверхед у GC в Cello конечно же, есть. Указатели в Cello сопровождаются дополнительной мета-информацией, которая хранится прямо перед данными. Это говорит о том, что указатели в Cello полностью совместимы с рабоче-крестьянскими указателями из Стандарта и могут без труда кооперироваться.

Вся информация о типах в большей мере является всего лишь списком экземпляров типоклассов и интерфейсов. Эти ребята себя очень хорошо зарекомендовали, поэтому они используются везде: начиная от GC, заканчивая документацией. Они позволяют использовать объекты в контексте их поведения. Это прикольно, потому что можно писать алгоритмы в обобщенной манере, опираясь только на вводные данные, но не на реальную реализацию той или иной структуры.

Cello, в принципе, довольно умен и может автоматически выводить поведения (behaviours) в большинстве случаев. Объекты в Cello можно печатать, сравнивать, хэшировать, сериализировать, сортировать копировать и вот это все. Короче говоря, райское наслаждение.

Объекты


Объекты в Cello начинаются с обыкновенных рабоче-крестьянских структур, которые ты так хорошо знаешь из С. Чуть позже Cello добавит в них немного мета-информации. Тут есть одно требование: определять структуры нужно без typedef. Например, давайте напишем структуру для хранения какой-то картинки, да на стероидах! Для этого нужно определить обыкновенную сишную структуру и зарегистрировать новополученный тип при помощи макроса Cello:

struct Image {
  uint64_t width;
  uint64_t height;
  unsigned char *data;
};

var Image = Cello(Image);

Обрати внимание, у нас появились две штуки. Оригинальный сишный тип Image и переменная, которая представляет тип в рантайме. По воле случая, мы тоже ее назвали Image. Ты скорее всего обратил внимание на этого подозрительного товарища по имени var. На самом деле var это всего лишь void*, тоесть обобщенный указатель, но стоит использовать первый вариант, для удобства.

В контексте базовых типов, это все. Больше ничего не нужно писать, за тебя все сделает компилятор. Теперь можно создавать переменные типа Image: что на стэке, что на куче. Помимо всего прочего, их можно напечатать, сравнить, закинуть в коллекцию и вот это все:

/* Allocate on Stack or Heap */
struct Image* x = $(Image, 0, 0, NULL);
struct Image* y = new(Image);

/* Print */
print("This is an image: %$\n", x);

/* Compare */
print("Images %$ and %$ are equal? %s\n", 
  x, y, eq(x, y) ? $S("Yes") : $S("No"));

/* Put in an Array */
struct Array* a = new(Array, Image, x, y);
print("Array of Images: %$\n", a);

В действительности, почти все основные типоклассы в Cello, по умолчанию, с реализацией. Но настоящая сила Cello проявляется тогда, когда мы начинаем расширять реализацию этих типоклассов.

Конструкторы и деструкторы


Наша сишная структура, Image, содержит указатель на какой-то участок памяти, который может быть выделен какой-то другой функцией. Если ты хочешь избежать утечек, надо убедиться, что мы вовремя эту память освобождаем. Теперь воспользуемся Cello, чтобы определить деструктор для Image:

void Image_Del(var self) {
  struct Image* i = self;
  free(i->data);
}

Ты можешь с легкостью привести аргумент self к сишному типу Image*. Это возможно, потому что указатели Cello (те, которые мы создаем с var) полностью совместимы с рабоче-крестьянскими указателями в С. Так как у тебя есть var-указатель из Cello, ты знаешь, что на нем висит опредленный сишный тип (прямо как здесь, в деструкторе), а значит, что можно абсолютно безопасно привести его к этому типу и разумеется, получить доступ к полям этого типа. В конкретно этом случае, мы вызываем free для указателя на данные из Image.

Чтобы зарегистрировать деструктор в Cello, ты захочешь передать его в макрос Cello, как экземпляр Instance нового типокласса New. Так как мы пока не хотим определять конструктор, то стоит просто передать NULL в соотв. поле:

var Image = Cello(Image, Instance(New, NULL, Image_Del));

Теперь, когда GC в Cello придет, чтобы разобраться с объектом Image, он вызовет наш деструктор. А чего, по-моему, круто!

Cахар, сахар, сахар


Daniel Holden написал Cello, чтобы местами упростить свою работу, так что тут хватает разнообразного сахара. Например, сокращенный синтаксис создания переменных или даже таблицы (sic!):

#include "Cello.h"

int main(int argc, char** argv) {

  /* Shorthand $ can be used for basic types */
  var prices = new(Table, String, Int);
  set(prices, $S("Apple"),  $I(12)); 
  set(prices, $S("Banana"), $I( 6)); 
  set(prices, $S("Pear"),   $I(55));

  /* Tables also support iteration */
  foreach (key in prices) {
    var val = get(prices, key);
    print("Price of %$ is %$\n", key, val);
  }

  return 0;
}

Или замысловатые range-циклы и прочие слайсы:

#include "Cello.h"

int main(int argc, char** argv) {

  var items = new(Array, Int, 
    $I( 8), $I( 5), $I(20), 
    $I(15), $I(16), $I(98));

  /* Iterate over indices using "range" */
  foreach (i in range($I(len(items)))) {
    print("Item Range %i is %i\n", i, get(items, i));
  }

  /* Iterate over every other item with "slice" */ 
  foreach (item in slice(items, _, _, $I(2))) {
    print("Item Slice %i\n", item);
  }

  return 0;
}


И это еще далеко не все...


На самом деле, возможности Cello не заканчиваются на приведенном мною в этой статье функционале, но это не беда, ведь с остальными штуками вы сможете ознакомиться с помощью документации. Кстати говоря, у Cello есть классный Quickstart, в котором автор покажет, как написать програму, которая интересным образом глитчует .tga-изображения. Настоятельно рекомендую ознакомиться!

Отвечая на вопрос, готов ли Cello к продакшну… тут нет однозначного ответа. Си, в основном, используется там, где нужна прямо таки maxxxимальная производительность — например, во встраиваемых системах. Захотят ли разработчики подобного софта тянуть за собой GC — это очень спорный вопрос и я склонен к отрицательному ответу. С другой стороны, над Cello довольно долгое время экспериментировали, так что в принципе, это штука рабочая. Я думаю, что фуллтайм С-программисты однозначно должны заценить.

Для тех, кому интересно, как эта шняшка устроена внутри, ссылочка на гитхаб. Помимо всего прочего, я также хотел бы сделать небольшой опрос по теме поста. Прошу отвечать на него только разработчиков, которые реально работают с языком С, остальных хочу попросить воздержаться.
Cтанете ли вы использовать Cello?

Проголосовало 548 человек. Воздержалось 243 человека.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Илья @namespace
карма
19,7
рейтинг 0,0
Я делаю разный софт на Go, Python и C++
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (57)

  • +3
    Всё что угодно кроме «Определенно, да» :)))
  • –3
    Не буду из-за одного только var.
    Хотя в целом плюшки приятные.
    Не хокку.
    • +3
      typedef void* var;
      

      Это ещё не самое ужасное. Вот, смотрите:

      #define is ==
      #define isnt !=
      #define not !
      #define and &&
      #define or ||
      #define in ,
      

      Зачем?
      • +3
        #define in,
        о_0
        Тяжёлая наркомания. Либо это не перечисление, а что-то другое, но тогда наркомания тяжелее тяжёлой.
        • +6
          Это типа для foreach (element in collection)
          • –11
            Обосраться, я не успеваю писать комментарии, а ты уже все раскрываешь!
      • +8
        Конкретно is, isn't, not, and и or — конечно же, полная ересь. Но in нужен для того, чтобы писать красивые foreach-циклы!
        • 0
          int main(int argc in char** argv)

          Из-за этого возникает ощущение, что «оллимпиадник» Cello сделал. Они любят… такое.
          • +2
            Ага,
            var a in *b;
            

            Или макросы вызывать:
            macro(in in in)
      • +14
        Между прочим,
        Alternative spellings для логических операторов есть в стандарте самого С, определены в <iso646.h>

        Primary 	Alternative
        &&	and
        &=	and_eq
        &	bitand
        |	bitor
        ~	compl
        !	not
        !=	not_eq
        ||	or
        |=	or_eq
        ^	xor
        ^=	xor_eq
        
        • +1
          А в С++ они даже являются не макросами, а полноценными операторами.
        • –2
          Ещё есть триграфы. В библиотеке это говно зачем?)
    • –7
      Тебе никто не запрещает использовать рабоче-крестьянский void*. Я конечно не ручаюсь в таком случае за твое здоровье, но твое право!
      • +1
        Что void*, что var — код не читабелен. Поди угадай, что за тип. Я бы предпочёл настоящую строгую типизацию, чем вообще её отсутствие.
        • 0
          Какое отношение строгая типизация имеет к явным аннотациям типов?
          • 0
            Такое, что ни в С, ни в С++ нет строгой типизации. Я бы предпочёл «дополнение» со строгой типизацией, чем всякую прочую ненужную фигню.

            Например, какой тип будет при записи: «var a = 1;»? Под это объявление подходит минимум восемь типов int'ов, плюс float и double (но их точно не будет выбрано).
            • 0
              В языке с действительно строгой типизацией вы не напишете var a = 1.5;, а в хорошем языке со строгой типизацией var a = 1; даст полиморфный тип (как тайпкласс Num в хаскеле, например).

              Строгую типизацию ни в плюсы, ни в C уже не впилить при всём желании, к сожалению. Но судить об этом по var или auto не стоит.
              • 0
                Я и не спорю, что var (почти) не имеет общего со строгой типизацией. В С++ строгую типизацию добавить элементарно на уровне компилятора. Только потеряется совместимость с имеющимися программами и придётся в большинстве арифметических операций вручную тип задавать… Пожалуй, мне и без строгой типизации неплохо программируется.
                • 0
                  Согласен, явное преобразование типов способно создать больше геморроя, чем отсутствие строгой типизации.
                • 0
                  А совместимость с имеющимися программами, библиотеками и кодом — одна из основных причин, почему люди выбирают С и С++, и почему С++ вообще взлетел в своё время.

                  А зачем задавать типы руками?
                  • 0
                    double a = 5.0 + 5;
                    При строгой типизации не скомпилируется, придётся писать double(5).
                    • 0
                      И в чём удобство такого?
                      • 0
                        А где я говорил, что это удобно? Я говорил, что мне перехотелось строгой типизации, глядя на это.)
                    • +3
                      Prelude> 5.5 + 5
                      10.5
                      Prelude> let a = 5
                      Prelude> let b = 5.5
                      Prelude> a + b
                      10.5
                      Prelude> :t a
                      a :: Num a => a
                      Prelude> :t b
                      b :: Fractional a => a
                      Prelude> :t a + b
                      a + b :: Fractional a => a
                      


                      Чудно!

                      Это Haskell, если что, строже особо некуда.
  • +12
    ШОК! Зачем же мне теперь все эти ваши Go/D/Nim/<впиши>, если С на стероидах решает все проблемы рода человеческого?!
    Cello добавляет поверх С дополнительный слой рантайма.
    мда… проблема рода человеческого в том, чтобы обеспечить почти всё это без рантайма.
  • +4
    Есть же Rust, который делает всё это и куда больше, только без оверхеда в рантайме. И как, кстати, у Cellо дела со строгой типизацией? Сдается мне, что многие синтаксические фишки не позволяют сохранить информацию о типе без поддержки компилятора, как тот же var.
    • –6
      Тоесть ты сейчас серьезно критикуешь, по-сути, Си, за то, что в нем нету плюшек из Rust? Тут товарищ сделал костыль для сишки и назвал его Cello. Зараза, в каждый тред о C/C++ прибежит фрик, угорающий по Rust и начнет доказывать, что последний обосраться какой крутой.

      Все понимают, что в Rust с его девятью типами указателей полная потокобезопасность, контроль лайфтайма и вот это все. Ясно, всем все понятно, спору нет — классный язык. Не нужно об этом постоянно напоминать.
      • +5
        Критикую статью за голословные восторгания, поскольку у Cello-таки остались нерешенные проблемы рода человеческого, а альтернативы уже объявлены ненужными. Я радею отнюдь не за превосходство Rust, Nim или любого другого инструмента над остальным, а за объективность статей на хабре.
        • –10
          Тоесть весь дух статьи, теги, все вот это… не натолкнули тебя на мысль, что может где-то я могу как-то преувеличивать значимость чего-то или где-то могу как-то повыгоняться? Каких же скучных сейчас делают человеков.
          • 0
            Эллочка по-рабоче-крестьянски завидует тебе, что слог так прекрасен, и вот это все.
  • +2
    Си, в основном, используется там, где нужна прямо таки maxxxимальная производительность — например, во встраиваемых системах.
    Там он используется скорее потому, что компилятора C++ (и Rust, ага) просто нет, или есть, но «ужас-ужас-ужас».
  • +14
    Получается, нишу C это заполнить не может (рантайм, гц, динамическая типизация). Зачем оно тогда вообще нужно? С такими свойствами полно других языков, только не являющихся набором костылей над С.
  • +2
    Как-то пробовал на нём писать «Песню про пиво», ощущения так себе.
  • –1
    Всегда считал, что вместо траты времени на создание новых языков, лучше написать хорошую библиотеку для того языка, который тебе нравится.

    Cello — прикольная штука, думаю это первая ласточка к созданию «упрощенных / безопасных» библиотек / оберток для С и С++.

    Там где нужна скорость — пишешь на чистом С / С++;
    Там где нужна супер скорость — пишешь asm {… }
    Там где скорость не критична, подключаешь Cello.h или еще что-то и быстренько ваяешь что-нибудь в стиле PHP.
    И все это в одной программе и на одном компиляторе — вот это было бы супер круто!!!

    На С писал программы только по работе для микроконтроллеров (других применений его в 21 веке не знаю), там скорость очень важна, поэтому Cello использовать никто не будет.

    А вот на C++ пишется много прикладных задач, в которых критических мест мало, вот там можно было бы добавить хорошую библиотеку типа php.h

    PHP чем хорош? Тем что там по любому чиху уже есть готовая стандартная ф-ция, не надо искать библиотеку (выбираешь из стандартных), не надо следить за освобождением памяти, автоматические типы переменных, простой интерфейс для ассоциативных массивов с теми же foreach и т.п…

    Когда начинал писать на PHP все время задавался вопросом: Почему такую же библиотеку не сделать для С++? Ведь по сути все стандартные библиотеки PHP написаны на С++, зачем придумывать новый язык если можно сделать удобную обертку, ибо C++ велик и могуч! :)

    В новомодном C++14, много фишек типа всяких лямбда и новой интерпретации auto, но имхо, их синтаксис туманит разум выросший на простом и понятном C++98. Вот если бы эти фишки применить для обертки библиотек php и их автоматической конвертации в код C++ по типу Cello… А там уже и не далеко до полного портирования любого php-кода в C++ (если разницу в синтаксисе через обертку минимизировать, то может даже до простого copy/past можно довести).

    Ну что, господа, как Вам идея?
    А может такая штука уже давно реализована, и я один ни х… не знаю? :)
    • 0
      Украл мой комментарий :-).
    • +1
      О… пояндексил и нашел phpcpp.h
      правда он для создания расширений на cpp для php сервера, и… не так удобен как хотелось (если бы просто повторить/приблизиться к синтаксису php), поэтому обычное использование библиотеки чисто для создания c++ программ не дает особого выигрыша в простоте
    • +8
      Вы предлагаете решать все задачи с помощью одного известного вам инструмента, потому что он «велик и могуч», но серебряной пули не бывает. Для написания сайта нужен совсем другой фреймворк, нежели для десктопного приложения или мобильной игры, и попытка сделать один универсальный приведет только к тому, что на нем будет одинаково сложно и неудобно писать любые приложения.
      • –3
        Ну… не знаю.
        Основной инструмент, который я использую для C++ это С++Builder.
        С помощью него сейчас из коробки можно написать и десктопное приложение (Vcl, FireMonkey), сайт (IntraWeb/VclForWeb, Indy) и мобильную игру (компилится в нативный, android, mac) и компоненты для работы с БД (SQLite, MySql, FireBird, MS SQL...)
        И все это я реально использую в своих проектах из одной IDE и на одном языке.

        PHP — юзаю больше для создания прототипов и доработки open source проектов используемых в нашей организации. Плюсы PHP, которых мне не хватает в C++ я описал выше.

        А то что С++ «велик и могуч» — это Вы не сомневайтесь :)
        На его базе с помощью оберток можно к синтаксису любого языка приблизиться (так сказать создать язык внутри языка), было бы время да голова на плечах.
        • +3
          2015
          • 0
            :) Других аргументов нет?
          • 0
            Пишу в 2015 на C++. И на Haskell ещё. Что не так?

            Впрочем, не нужны все эти обёртки там, и мне, как выросшему на C++03, все эти фишки из C++11/C++14 голову не туманят совершенно. Вопрос практики, ИМХО.
    • +4
      Посмотрите в сторону Vala. Не совсем то, но транслируется в си, потом компилируй чем хочешь.
      • –3
        Спасибо. Интересная штука, но действительно… не совсем то, чего душа требует.
        Хочется просто подключить xxx.h и пользоваться плюшками, а не залазить в другую IDE, транслировать из нее, потом компилить, чтоб изменения проверить… неудобно.
    • +1
      В данном случае основной недостаток C++ — что он не отсекает возможность писать небезопасный код. На фоне этой небезопасности в плане «диких» указателей и утечек памяти все «улучшения через макросы» выглядят как «ложка мёда к бочке дёгтя» — ни на хлеб положить ни телегу смазать. Как если бы это были макронадстройки над языком ассемблера. И как, в прочем, и аналогичные надстройки на фортом, которые регулярно появляются just for fun.
      • –1
        Не безопасный код — понятие растяжимое, его можно писать на любом языке.
        Типа взаимный Lock потоков из-за неверного условия в if Или случайно затерявшаяся строчка посреди программы exec(«format c:») :-)… Все зависит от того, кто этот код пишет.

        Дикие указатели с new и забытыми/не продуманными delete — удел школьников и дилетантов.
        Профессионалы если такое используют — то делают это обосновано (очень очень нужна скорость) проектируют и тестируют архитектуру для всех возможных вариантов событий на 100 ходов веред.

        Там где скорость не критична и более важно удобство — ВСЕГДА нормальными программистами используются смарт-поинтеры (причем при правильной архитектуре достаточно лишь auto_ptr) и контейнеры с автоочисткой.

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

        Мне приходилось писать на разных ЯП от asm, бэйсика, паскаля, js, vb, c#, php, до языков функциональных блоковых диаграм и LAD. Но оптимальным для себя выбрал именно c++ в совокупности с IDE С++Builder которая всегда в авангарде технологий и имеющая кучу готовых компонентов, которые на данный момент позволяют решать абсолютно любые современные задачи за вполне разумное время (от построения дестопного GUI или web-сервисов до программирования МК или мобильных устройств).

        Для счастья всего лишь не хватает библиотеки, которая упростила бы (сократила код) для работы в критически не важных по скорости местах программы по типу php и его стандартных библиотек.

        Vala — это не совсем то. Основная ее идея — полное создание приложения на Vala (с возможностью вызова C ф-ций) и последующей компиляции в продакшен на C. Я же говорю об основном коде на C++ и подключением к этому коду библиотеки обеспечивающей упрощение синтаксиса и автоматическую безопасность. Что упростит и разработку и отладку.
        • +1
          unique_ptr только, пожалуйста, вместо auto_ptr.
          • –1
            Спасибо за бдительность!
            unique_ptr эт для С++11, в моем случае поддерживаемые проекты чуток по старше :)
    • 0
      Подобные бубнотанцы с макросами могут быть нужны, если вдруг необходима компиляция кода, написанного на более безопасном языке типа Java или C#, компилятором С или С++. Типа трансляции в Javascript в GWT. Тут гарантией безопасности является компилятор исходного языка, а «вылизать» транслятор и макробиблиотеку теоретически нужно только один раз.
  • +2
    Для продакшена есть С++ (даже без D, Go, Rust и Nim). Еще есть C++/Boost, в котором тоже немало накручено… но он как-то используется более широко, поэтому доверия больше.
    А это — любопытно, не более того. Любопытный пример того, что должно быть в языках программирования, но нет, отличный довод в пользу того, что в языках должно быть много фич (аналогично Бусту для С++).
  • +3
    Человек сделал vala, причем сделал намного хуже оригинала. Не нужно
    • 0
      во, а я все думал, где я про похожее уже читал. Точно, vala получше будет.
    • 0
      Все были бы благодарны, если бы Вы написали бы обзорную статью про vala.
  • 0
    Да кто хотел изучить что-то помимо C, давно изучил, и продолжают изучать новые языки. Кто не хочет — тем хоть что предложи, найдут аргументы против.
    Это я не к тому, что описанный в статье велосипед хорош.
  • 0
    Согласно Десятому правилу Гринспана, лучшей и наиболее естественной надстройкой над фортраном или С является Common Lisp ;-). Ну или в крайнем случае другой функциональный язык ;)

    А всё почему? А потому, что незаметно, чтоб авторы языка С удосужились сделать нормальную поддержку использования блоков кода в качестве аргументов макроса.
    • 0
      А как со скоростью такой надстройки? Вы точно уверены что речь о надстройке над C, а не над Forth? Поскольку функциональный код можно очень легко транслировать в быстрый Forth-код, просто превратив выражения вроде +(a,b) в код вида a b +
      Но вот способа превратить функциональный код в быстрый C-код я не вижу.
  • 0
    Хочу! (^_^)

    А с OpenGL эта библиотека насколько хорошо совместима? (кто-нибудь проверял?)
  • 0
    Таки скачал, попробовал парочку своих (и вполне живых в продакшне) сишностей к этому приспособить.
    В общем, поигрался и выплюнул. Голосовал как «Посмотрю в его сторону и может быть…».

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.