Высокоуровневый С или пару слов о 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?

Проголосовал 1 человек. Воздержавшихся нет.

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

ссылка на оригинал статьи http://habrahabr.ru/post/262471/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *