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 довольно долгое время экспериментировали, так что в принципе, это штука рабочая. Я думаю, что фуллтайм С-программисты однозначно должны заценить.
Для тех, кому интересно, как эта шняшка устроена внутри, ссылочка на гитхаб. Помимо всего прочего, я также хотел бы сделать небольшой опрос по теме поста. Прошу отвечать на него только разработчиков, которые реально работают с языком С, остальных хочу попросить воздержаться.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (57)
maaGames
13.07.2015 17:29-3Не буду из-за одного только var.
Хотя в целом плюшки приятные.
Не хокку.a553
13.07.2015 17:32+3typedef void* var;
Это ещё не самое ужасное. Вот, смотрите:
#define is == #define isnt != #define not ! #define and && #define or || #define in ,
Зачем?maaGames
13.07.2015 17:34+3#define in,
о_0
Тяжёлая наркомания. Либо это не перечисление, а что-то другое, но тогда наркомания тяжелее тяжёлой.
namespace Автор
13.07.2015 17:34+8Конкретно is, isn't, not, and и or — конечно же, полная ересь. Но in нужен для того, чтобы писать красивые foreach-циклы!
encyclopedist
13.07.2015 18:02+14Между прочим,
Alternative spellings для логических операторов есть в стандарте самого С, определены в <iso646.h>
Primary Alternative && and &= and_eq & bitand | bitor ~ compl ! not != not_eq || or |= or_eq ^ xor ^= xor_eq
namespace Автор
13.07.2015 17:33-7Тебе никто не запрещает использовать рабоче-крестьянский
void*
. Я конечно не ручаюсь в таком случае за твое здоровье, но твое право!maaGames
13.07.2015 17:35+1Что void*, что var — код не читабелен. Поди угадай, что за тип. Я бы предпочёл настоящую строгую типизацию, чем вообще её отсутствие.
0xd34df00d
14.07.2015 18:46Какое отношение строгая типизация имеет к явным аннотациям типов?
maaGames
14.07.2015 18:56Такое, что ни в С, ни в С++ нет строгой типизации. Я бы предпочёл «дополнение» со строгой типизацией, чем всякую прочую ненужную фигню.
Например, какой тип будет при записи: «var a = 1;»? Под это объявление подходит минимум восемь типов int'ов, плюс float и double (но их точно не будет выбрано).0xd34df00d
14.07.2015 19:00В языке с действительно строгой типизацией вы не напишете
var a = 1.5;
, а в хорошем языке со строгой типизациейvar a = 1;
даст полиморфный тип (как тайпкласс Num в хаскеле, например).
Строгую типизацию ни в плюсы, ни в C уже не впилить при всём желании, к сожалению. Но судить об этом поvar
илиauto
не стоит.maaGames
14.07.2015 19:07Я и не спорю, что var (почти) не имеет общего со строгой типизацией. В С++ строгую типизацию добавить элементарно на уровне компилятора. Только потеряется совместимость с имеющимися программами и придётся в большинстве арифметических операций вручную тип задавать… Пожалуй, мне и без строгой типизации неплохо программируется.
Idot
14.07.2015 19:11Согласен, явное преобразование типов способно создать больше геморроя, чем отсутствие строгой типизации.
0xd34df00d
14.07.2015 19:17А совместимость с имеющимися программами, библиотеками и кодом — одна из основных причин, почему люди выбирают С и С++, и почему С++ вообще взлетел в своё время.
А зачем задавать типы руками?maaGames
14.07.2015 19:27double a = 5.0 + 5;
При строгой типизации не скомпилируется, придётся писать double(5).0xd34df00d
14.07.2015 19:30+3Prelude> 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, если что, строже особо некуда.
ababo
13.07.2015 17:46+12ШОК! Зачем же мне теперь все эти ваши Go/D/Nim/<впиши>, если С на стероидах решает все проблемы рода человеческого?!
Cello добавляет поверх С дополнительный слой рантайма.
мда… проблема рода человеческого в том, чтобы обеспечить почти всё это без рантайма.
impwx
13.07.2015 18:00+4Есть же Rust, который делает всё это и куда больше, только без оверхеда в рантайме. И как, кстати, у Cellо дела со строгой типизацией? Сдается мне, что многие синтаксические фишки не позволяют сохранить информацию о типе без поддержки компилятора, как тот же
var
.namespace Автор
13.07.2015 18:05-6Тоесть ты сейчас серьезно критикуешь, по-сути, Си, за то, что в нем нету плюшек из Rust? Тут товарищ сделал костыль для сишки и назвал его Cello. Зараза, в каждый тред о C/C++ прибежит фрик, угорающий по Rust и начнет доказывать, что последний обосраться какой крутой.
Все понимают, что в Rust с его девятью типами указателей полная потокобезопасность, контроль лайфтайма и вот это все. Ясно, всем все понятно, спору нет — классный язык. Не нужно об этом постоянно напоминать.impwx
13.07.2015 18:34+5Критикую статью за голословные восторгания, поскольку у Cello-таки остались нерешенные проблемы рода человеческого, а альтернативы уже объявлены ненужными. Я радею отнюдь не за превосходство Rust, Nim или любого другого инструмента над остальным, а за объективность статей на хабре.
namespace Автор
13.07.2015 18:47-10Тоесть весь дух статьи, теги, все вот это… не натолкнули тебя на мысль, что может где-то я могу как-то преувеличивать значимость чего-то или где-то могу как-то повыгоняться? Каких же скучных сейчас делают человеков.
DmitryKoterov
18.07.2015 22:44Эллочка по-рабоче-крестьянски завидует тебе, что слог так прекрасен, и вот это все.
dea
13.07.2015 19:29+2Си, в основном, используется там, где нужна прямо таки maxxxимальная производительность — например, во встраиваемых системах.
Там он используется скорее потому, что компилятора C++ (и Rust, ага) просто нет, или есть, но «ужас-ужас-ужас».
FoxCanFly
13.07.2015 20:30+14Получается, нишу C это заполнить не может (рантайм, гц, динамическая типизация). Зачем оно тогда вообще нужно? С такими свойствами полно других языков, только не являющихся набором костылей над С.
FreeMind2000
13.07.2015 20:49-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 можно довести).
Ну что, господа, как Вам идея?
А может такая штука уже давно реализована, и я один ни х… не знаю? :)FreeMind2000
13.07.2015 22:18+1О… пояндексил и нашел phpcpp.h
правда он для создания расширений на cpp для php сервера, и… не так удобен как хотелось (если бы просто повторить/приблизиться к синтаксису php), поэтому обычное использование библиотеки чисто для создания c++ программ не дает особого выигрыша в простоте
impwx
13.07.2015 23:04+8Вы предлагаете решать все задачи с помощью одного известного вам инструмента, потому что он «велик и могуч», но серебряной пули не бывает. Для написания сайта нужен совсем другой фреймворк, нежели для десктопного приложения или мобильной игры, и попытка сделать один универсальный приведет только к тому, что на нем будет одинаково сложно и неудобно писать любые приложения.
FreeMind2000
14.07.2015 01:13-3Ну… не знаю.
Основной инструмент, который я использую для C++ это С++Builder.
С помощью него сейчас из коробки можно написать и десктопное приложение (Vcl, FireMonkey), сайт (IntraWeb/VclForWeb, Indy) и мобильную игру (компилится в нативный, android, mac) и компоненты для работы с БД (SQLite, MySql, FireBird, MS SQL...)
И все это я реально использую в своих проектах из одной IDE и на одном языке.
PHP — юзаю больше для создания прототипов и доработки open source проектов используемых в нашей организации. Плюсы PHP, которых мне не хватает в C++ я описал выше.
А то что С++ «велик и могуч» — это Вы не сомневайтесь :)
На его базе с помощью оберток можно к синтаксису любого языка приблизиться (так сказать создать язык внутри языка), было бы время да голова на плечах.stepanp
14.07.2015 03:00+32015
0xd34df00d
14.07.2015 19:11Пишу в 2015 на C++. И на Haskell ещё. Что не так?
Впрочем, не нужны все эти обёртки там, и мне, как выросшему на C++03, все эти фишки из C++11/C++14 голову не туманят совершенно. Вопрос практики, ИМХО.
merhalak
13.07.2015 23:43+4Посмотрите в сторону Vala. Не совсем то, но транслируется в си, потом компилируй чем хочешь.
FreeMind2000
14.07.2015 01:20-3Спасибо. Интересная штука, но действительно… не совсем то, чего душа требует.
Хочется просто подключить xxx.h и пользоваться плюшками, а не залазить в другую IDE, транслировать из нее, потом компилить, чтоб изменения проверить… неудобно.
Flammar
14.07.2015 17:24+1В данном случае основной недостаток C++ — что он не отсекает возможность писать небезопасный код. На фоне этой небезопасности в плане «диких» указателей и утечек памяти все «улучшения через макросы» выглядят как «ложка мёда к бочке дёгтя» — ни на хлеб положить ни телегу смазать. Как если бы это были макронадстройки над языком ассемблера. И как, в прочем, и аналогичные надстройки на фортом, которые регулярно появляются just for fun.
FreeMind2000
14.07.2015 19:18-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++ и подключением к этому коду библиотеки обеспечивающей упрощение синтаксиса и автоматическую безопасность. Что упростит и разработку и отладку.0xd34df00d
14.07.2015 19:31+1unique_ptr
только, пожалуйста, вместоauto_ptr
.FreeMind2000
14.07.2015 19:49-1Спасибо за бдительность!
unique_ptr эт для С++11, в моем случае поддерживаемые проекты чуток по старше :)
Flammar
14.07.2015 18:45Подобные бубнотанцы с макросами могут быть нужны, если вдруг необходима компиляция кода, написанного на более безопасном языке типа Java или C#, компилятором С или С++. Типа трансляции в Javascript в GWT. Тут гарантией безопасности является компилятор исходного языка, а «вылизать» транслятор и макробиблиотеку теоретически нужно только один раз.
NeoCode
13.07.2015 21:15+2Для продакшена есть С++ (даже без D, Go, Rust и Nim). Еще есть C++/Boost, в котором тоже немало накручено… но он как-то используется более широко, поэтому доверия больше.
А это — любопытно, не более того. Любопытный пример того, что должно быть в языках программирования, но нет, отличный довод в пользу того, что в языках должно быть много фич (аналогично Бусту для С++).
guai
14.07.2015 15:31Да кто хотел изучить что-то помимо C, давно изучил, и продолжают изучать новые языки. Кто не хочет — тем хоть что предложи, найдут аргументы против.
Это я не к тому, что описанный в статье велосипед хорош.
Flammar
14.07.2015 16:23Согласно Десятому правилу Гринспана, лучшей и наиболее естественной надстройкой над фортраном или С является Common Lisp ;-). Ну или в крайнем случае другой функциональный язык ;)
А всё почему? А потому, что незаметно, чтоб авторы языка С удосужились сделать нормальную поддержку использования блоков кода в качестве аргументов макроса.Idot
14.07.2015 18:36А как со скоростью такой надстройки? Вы точно уверены что речь о надстройке над C, а не над Forth? Поскольку функциональный код можно очень легко транслировать в быстрый Forth-код, просто превратив выражения вроде +(a,b) в код вида a b +
Но вот способа превратить функциональный код в быстрый C-код я не вижу.
Idot
14.07.2015 18:25Хочу! (^_^)
А с OpenGL эта библиотека насколько хорошо совместима? (кто-нибудь проверял?)
kloppspb
15.07.2015 23:32Таки скачал, попробовал парочку своих (и вполне живых в продакшне) сишностей к этому приспособить.
В общем, поигрался и выплюнул. Голосовал как «Посмотрю в его сторону и может быть…».
kentastik
Всё что угодно кроме «Определенно, да» :)))