Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Posts Tagged ‘c++

Не нравится ООП? Делайте свой язык программирования!

25 комментариев

В интернете нынче модно говорить что «ООП это шлак», и многие мечтают сделать свой собственный язык программирования но чего-то боятся. А на самом деле, если подойти с умом, тут все просто. Серьезно! Я знаю, вы хотите мне возразить, что дескать…

  • Писать свой компилятор в native code/IL/bytecode слишком сложно — а и не надо!! Вы понимаете, что существующие компиляторы вроде С или Java оттачивались годами, сотнями людей? Почему бы не воспользоваться всем этим богатством, компилируя ваш язык в тот же С/C++, и потом получая от С/C++ компилятора все оптимизации и плюшки?

  • Меня не прет идея возиться с кастомными форматами файлов для лексинга и парсинга, это депрессивно — а и не надо!! Просто возьмите фреймворк, который поддерживает парсеростроение прямо в коде.

  • У меня сейчас весь код на Java/C#/C++ написан, как я сделаю interop? — да очень просто, ведь с подходом транскомпиляции, вы можете транслировать свой язык в любой из вышеперечисленных, генеря и потом потребляя любой интерфейс, причем в обе стороны.

  • Язык-то я сделаю, а как насчет поддержки языка в моей любимой IDE? — о, ну это уже высший полет. Для начала, постарайтесь сделать лаконичный маленький язык, для которого инструменты не критичны. А потом можно научиться и тулы поддерживать (или писать свои).

Ну что, убедил?

Чтобы показать насколько это все просто, вот небольшой пример: представим, что вы хотите расширить определение С-образной глобальной функции, добавив следующие фичи:

  • Определить свой набор «коротких» типов переменных, вроде i32 или f64 на замену int и double.

  • Передавать аргументы в формате x,y : i32, то есть переиспользуя определение типа для нескольких сразу.

  • Добавлять в тело функции определения переменных вроде x = 5 так чтобы, при условии что x не имя параметра, это преврашалось в полноценную декларацию переменной, а иначе просто присваивалось значиние.

Для начала такой фичесет подойдет? Я знаю что мало, но я тут и не пытаюсь целый язык сделать. Вот как это будет выглядеть:

Наш язык… …станет вот этим
void foo(x,y:i32, z:f64)
{
  x = 5;
  w = 123;
}
void foo(int32_t x, int32_t y, double z)
{
  x = 5;
  int w = 123;
}

Структуры для языка

Во всех языках есть механизмы построения парсеров. Я возьму C++ и Boost.Spirit, для примера, но вообще язык тут особого значения не имеет. Для начала давайте сделаем новые типы вроде f32 вместо float:

struct numeric_types_ : qi::symbols<wchar_t, wstring>
{
  numeric_types_()
  {
    add(L"i32", L"int32_t");
    add(L"f32", L"float");
    add(L"f64", L"double");
  }
} numeric_types;

Теперь определяем функцию:

struct function
{
  wstring name;
  vector<parameter> params;
  vector<assignment_statement> assignments;
 
  // поиск параметра по имени; реализация банальна
  boost::optional<const parameter&> find_parameter(const wstring& name) const;
};

У любой функции есть имя, она берет сколько-то там параметров (ну, деклараций параметров, но краткость сестра таланта), и у нее есть тело, которое в нашем случае будет состоять исключительно из присвоений значений переменным (не очень практично, I know).

Поскольку мы умеем определять аргументы в стиле x,y:i32, т.е. несколько с одним типом, собственно структур parameter мы определим вот так:

struct parameter
{
  vector<wstring> names;
  wstring type;
};

Ну и наконец присвоение значений переменным можно сделать вот так:

struct assignment_statement
{
  wstring variable_being_assigned;
  wstring value;
  wstring infer_type() const
  {
    if (value.find(L'.') == wstring::npos)
      return L"int"s;
    return L"float"s;
  }
};

Выше показан г~код для определения типа, думаю вы понимаете что в последствии это можно пофиксить.

Всё, структуры готовы, можно строить парсер. (На самом деле есть еще этап их адаптации через Boost.Fusion, но это implementation-specific деталь, если что гляньте в сорцы.)

Парсер

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

template<typename Iterator>
struct function_parser : qi::grammar<Iterator, function(), space_type>
{
  function_parser() : function_parser::base_type(start)
  {
    using qi::lit;
    param %= 
      +alnum % ','
      >> ':'
      >> numeric_types
      >> -char_(',');
    assignment %=
      +alnum
      >> '='
      >> +alnum
      >> ';';
    start %= lit("void ")
      >> +(char_ - '(')
      >> '('
      >> *param
      >> ')'
      >> '{'
      >> *assignment
      >> '}';
  }

  qi::rule<Iterator, parameter(), space_type> param;
  qi::rule<Iterator, assignment_statement(), space_type> assignment;
  qi::rule<Iterator, function(), space_type> start;
};

Для начала, вот эти qi::rule просто говорят парсеру как то что он распарсит ложится на структуры что мы определили ранее. Например, вот хочется распарсить присваивание вроде x = 3, что это? Это идентификатор (то есть, 1 и более alphanumeric символов), потом =, потом еще раз набор символов и в конце ;.

Конкретно в Boost.Sprit, в отличии от регулярок, «один и более» записывается как + до типа символа, т.е. +alnum. То есть + означает «один и более», * — «сколько угодно», и так далее. Вот и получается что присваивание мы распарсили, а поскольку наш qi::rule мэпит его на assignment_statement, поля этой структуры будут присвоены автоматически. Это гениально, или как?

То же и с другими частями языка. Хочешь распарсить несколько переменных через запятую и запихнуть их в вектор? Пишем +alnum % ',' где оператор % – это как сказать *(+alnum >> ','), только короче. Что тоже удобно.

Так вот, парсер у нас готов, можно парсить. На Spirit это делается вот так:

template<typename Iterator>
wstring parse(Iterator first, Iterator last)
{
  using boost::spirit::qi::phrase_parse;
  function f;
  function_parser<wstring::const_iterator> fp{};
  auto b = phrase_parse(first, last, fp, space, f);
  if (b)
  {
    return render(f);
  }
  return wstring(L"FAIL");
}

…где render() – это функция которая обходит то что мы напарсили и генерит из этого чистейший, готовый к компиляции С (никто не мешает вам выводить сразу в N разных языков).

Pretty print

Короче, мы попарсили функцию, получили из нее ООП структуры, ну и теперь можно из них чего-нибудь нагенерить. Для этого их нужно обойти, что в нашем конкретном случае не сложно. Сначала пишем название функции:

inline wstring render(const function& f)
{
  wostringstream s;
  // name of the function (assume void-returning)
  s << "void " << f.name << "(";

Потом выводим параметры, не забывая что у нас может быть несколько имен для одного и того же типа аргумента:

// each of the parameters
const auto param_count = f.params.size();
for (auto i = 0u; i < param_count; ++i)
{
  auto& p = f.params[i];
  for (int j = 0; j < p.names.size(); ++j)
  {
    s << p.type << " " << p.names[j];
    if (j + 1 < p.names.size()) s << ", ";
  }
  if (i + 1 < param_count) s << ", ";
}
s << ")\r\n{\r\n";

А потом, аккуратненько, присваивания. Не забываем что тип нужно прописывать только если переменная не фигурирует где-то в параметрах функции:

// each of the assignments
const auto assign_count = f.assignments.size();
for (auto i = 0u; i < assign_count; ++i)
{
  s << "  ";
  auto& a = f.assignments[i];
  auto type = a.infer_type();
  bool is_param = f.find_parameter(a.variable_being_assigned) != boost::none;
  if (!is_param)
    s << type << " ";
  s << a.variable_being_assigned << " = " << a.value << ";\r\n";
}
s << "}";
return s.str();

Вот собственно и всё. Тут в принципе можно реализовать «полноценный» Visitor, если хочется.

Заключение

Как вы поняли, тут остался шаг компиляции полученного кода — думаю всем итак очевидно как это делать, это зависит от языка который вы нагенерили. Вообще я ратую за «портативный» C/C++, но решать в конечном счете вам.

Если хотите сорцы проекта, они тут. Мой пример на С++, но вы можете реализовать свой язык на чем угодно. Мораль в том что создать сейчас свой кросс-компилируемый язык легко, поэтому вместо того чтобы ныть про ООП и воевать с ветряными мельницами, проще сесть и запилить что-то своё. Так что садитесь и пишите спеку вашего чудо-юдо языка. Удачи!

Реклама

Written by Dmitri

2 сентября 2016 at 16:10

Опубликовано в Programming

Tagged with , ,

Попытки отражения garbage-collected структур на ужасный неуправляемый С++

Если вы вдруг решили сконвертировать C# в C++, то большинство задач конверсии – тривиальны. int меняется на int32_t, string на std::wstring (хотя на самом деле просто string для большинства людей которым unicode не нужен). Ну и так далее. Различия в большинстве случаев несущественны.

Основной задачей, как ни странно, является конверсия reference типов, т.к. в C# можно создавать массивы, дженерики и просто ref-типы и пускать все на самотек дескать “когда-нибудь освободится”, а вот на C++ так делать нельзя. Напомню, что в С++ есть вот такие варианты:

  • Аллоцировать вещи на стеке. К сожалению, за исключением примитивных типов, в C# никто так не делает. Чтобы C#ный объект который кто-то аллоцировал через new аллоцировать в С++ коде на стеке, нужно быть увереным что объект маленький и что он действительно только вот тут, в локальном scope, что его никто не потребляет. Зато стек почистится по мере выхода их скоупа, что как бы неплохо.

  • Аллоцировать вещи вручную через new/delete: это нереально в принципе т.к. если идти по этому пути, то у каждого типа может быть свой набор ссылок на весь мир, ну и… вы поняли. Получится ад. Не вариант.

  • Использовать умные указатели так, как их нужно использовать. То есть например если у вас есть фабрика которая порождает объекты и выдает их наружу, то вы можете возвращать unique_ptr. И так далее. Одна лишь проблема: как понять какой указатель нужен? Опять нужен глубинный анализ, а это сложно.

  • Использовать везде shared_ptr, т.к. это как раз тот указатель, который можно либерально копировать повсюду и при этом не получить ситуацию когда вы пытаетесь вызвать что-то у объекта, который уже был перемещен куда-то.

Инициализация и присваивание

Отдельной пролемой стоят всякие инициализации и присваивания. Например, представим что вы создаете человека, и передаете строку. Принято ведь писать

class Person
{
  string name;
public:
  Person(const string name) : name(name) {}
};

Опаньки! А мы-то думали что все не-примитивные объекты будут shared_ptr. В результате получается, что

  • Все типы которые мы считаем “маленькими” (а это включает всякие int, float и так далее) передаются by value

  • Строки и прочие типы которые нормально себя ведут в нынешней системе типов мы передаем как const ref

  • А вот все остальное – да, проблуем использовать shared_ptr

Полноценный пример

Давайте типичный пример с человеком у которого есть адрес:

struct Address
{
  int HouseNumber;
  string StreetName;
  Address(int houseNumber, const string streetName)
    : HouseNumber(houseNumber), StreetName(streetName) {}
};

Я намеренно использую struct и C#-ное именование а также не использую properties т.к. автосвойство C# можно смело конвертировать в поле C++ without loss of generality. Так вот, у нас есть адрес который берет const string во втором аргументе и это как бы правильно.

Теперь посмотрим на человека, который обладает адресом:

struct Person
{
  int Age;
  string Name;
  shared_ptr&lt;Address&gt; HomeAddress;
  Person(int age, const string name, shared_ptr&lt;Address&gt; homeAddress)
    : Age(age), Name(name), HomeAddress(homeAddress) {}
};

Вроде тоже неплохо. Сразу понятно что подсовывать Address нам не нужно, и что нам нужна “управляемая” версия. Теперь все это можно потреблять:

auto address = make_shared&lt;Address&gt;(221, &quot;Baker St&quot;);
auto person = make_shared&lt;Person&gt;(40, &quot;Sherlock&quot;, address);
cout &lt;&lt; person-&gt;Name &lt;&lt; endl;

Более того, мы можем не нервничать насчет удаления address если его “хозяин” надоел и его грохнули:

shared_ptr&lt;Address&gt; addr;
{
  auto address = make_shared&lt;Address&gt;(221, &quot;Baker St&quot;);
  auto person = make_shared&lt;Person&gt;(40, &quot;Sherlock&quot;, address);
  addr = person-&gt;HomeAddress;
}
cout &lt;&lt; addr-&gt;StreetName &lt;&lt; endl;

Ну ок, вроде оно как-то работает. Хотя тут мне подумалось… а ведь по идее можно и адрес было передавать как const shared_ptr<Address>, не так ли?

Ладно, что там следующее?

Массивы

Массивы – это вообще больное место. В C# есть два разных типа – прямоугольные [,] и угловатые [][], в С++ существует только 2й вариант. Но проблема даже не в этом. Проблема в том, как вообще представить N-мерный массив чего либо через умный указатель.

Начнем с простого: у человека несколько имен и это как-то нужно хранить. Тут, самое безопасное – это vector<string>. В принципе, со строками мы можем себе позволить такой вот расклад когда мы копируем by value. Но как насчет набора адресов?

Одномерный набор адресов в C# подразумевает конверсию как List<T> так и T[] в vector<shared_ptr<T>> – конечно, для тех типов где shared_ptr нужен, т.е. не для для примитивов или string:

struct Person
{
  int Age;
  vector&lt;string&gt; Names;
  typedef vector&lt;shared_ptr&lt;Address&gt;&gt; AddressCollection;
  AddressCollection Addresses;
  Person(int age, const vector&lt;string&gt; names, const AddressCollection addresses)
    : Age(age),
      Names(names),
      Addresses(addresses)  {  }
};

Ну а тут можно воспользоваться любезно предоставленым typedef’ом и проинициализировать все это счастье в стиле С++11:

auto person = make_shared&lt;Person&gt;(23, 
  vector&lt;string&gt;{&quot;Janes&quot;, &quot;Jimmy&quot;},
  Person::AddressCollection{ 
    make_shared&lt;Address&gt;(123, &quot;Hull Rd&quot;), 
    make_shared&lt;Address&gt;(23, &quot;Sesame St.&quot;)});
for (auto addr  : person-&gt;Addresses)
{
  cout &lt;&lt; addr-&gt;StreetName &lt;&lt; endl;
}

А что же насчет std::array или например boost::shared_array? Ну, std::array на самом деле требует (сразу!) известную длину. То есть если у вас в C# написано например что int i = new int[3] то тогда да, вам повезло:

struct Person
{
  array&lt;shared_ptr&lt;Address&gt;, 2&gt; HomeAndWorkAddresses;
  explicit Person(const array&lt;shared_ptr&lt;Address&gt;, 2&gt; home_and_work_addresses)
    : HomeAndWorkAddresses(home_and_work_addresses)
  {
  }
};

От кода выше в принципе уже должно тошнить, но для тех кто не понял в чем соль, вот то, как выглядит инициализация сего счастья:

array&lt;shared_ptr&lt;Address&gt;, 2&gt; addresses = { {
  make_shared&lt;Address&gt;(123, &quot;London Rd&quot;),
  make_shared&lt;Address&gt;(23, &quot;Shirley Ave&quot;)
} };
auto person = make_shared&lt;Person&gt;(addresses);
cout &lt;&lt; &quot;Work: &quot; &lt;&lt; person-&gt;HomeAndWorkAddresses[1]-&gt;StreetName &lt;&lt; endl;

Нда, мне определенно не нравится std::array. В большинстве случаев, мне кажется, лучше всего использовать vector<T>, vector<vector<T>> и так далее.

Полузаключение

На самом деле, то, что я описал – это верхушка айсберга. Например, допустим что мы допускаем хранения vector<string> но ведь по сути дела для передачи этой коллекции куда либо (а мало ли зачем?) мы не можем передавать ее by value (это расточительно!), и с другой стороны, она не shared_ptr<T>, то есть единственный наш вариант – это передавать ее как const vector<T>. Хмм, а вообще это не такая уж и плохая идея.

Вообщем, в этом посте я для себя вывел набор (вполне вероятно ошибочных) правил по тому, как конвертировать reference-конструкты C# в нечно С++ное чем можно хоть как-то управлять. Возможно в последствии построить поверх всего этого какие-то дополнительные оптимизации. Посмотрим…

Written by Dmitri

30 января 2015 at 1:20

Опубликовано в Programming

Tagged with , ,

Как выглядит разработка для Blackberry 10

27 комментариев

Несмотря на то что мне повезло в жизни работать с достаточно продвинутыми IDE, я все же иногда смотрю на другие среды разработки дабы понять как же все там плохо, что можно улучшить, и главное понять какой же вообще разработческий experience у того кто пишет для этой системы. Вот недавно я решил узнать положение дел в стеке Blackberry, особенно если учесть что недавно был выпущен их Q10 (жаль что по нереальной цене).

Языки и Технологии

Вот у Android все понятно: программы пишутся на Java. Для iOS тоже все более менее понятно: нужно использовать Objective-C, хотя Xamarin предлагает разрабатывать эти приложения в Visual Studio на C# (я это делаю, т.к. хочется использовать R#, и отладка из Студии реально работает, но это дорогая опция). На нишевых платформах все очень неоднородно — под Windows Phone можно писать на C#, C++ или WinJS (это адское месиво из HTML, CSS и JS) а на WinJS для WP8 вроде как нельзя писать. На Blackberry, правда, все еще хуже. Варианты примерно такие:

  • Использовать Java, но это только для «старых» версий, т.е. не для современной BB10. Отпадает сразу.

  • Портирование приложений Android. Ход хороший, конечно, но уверен что реализовано не без косяков. К тому же, не катит для тех, у кого приложений нет вообще.

  • Адаптация приложений Adobe AIR. Та же ситуация что и с Андроидом, плюс я не знаю кто нынче вообще кроме Adobe использует AIR. Последнее приложение которое я видел на AIR был «тяжелый» TweetDeck, но потом он магически материализовал себя как приложение Google Chrome и потребность в Standalone версии отпала вообще.

  • С++. Аааааа! Для написания «современных» BB10 приложений нужно использовать С++!

Я ничего не имею против плюсов, но видев тот адъ который творился с использованием плюсов для Symbian (привет двухэтапное создание объектов), энтузиазма у меня особого не наблюдалось. Тем не менее, решил скачать SDK.

Создание проекта

Всегда интересно, что же предлагает IDE на момент создания проекта. Ведь для мобильных устройств нужно настроить как минимум deployment targets, а это та еще задача, пусть у BB10 еще и нет той фрагментации что у Андроида. Итак, поехали.

Cascades – это то как называется новый интерфейс BB10. Следующий этап – выбор шаблона проекта, при условии что мы решили делать UI а не игру на OpenGL:

Далее все интересней – после генерации проекта (был еще один скрин для названия, я его не стал приводить), появляется мегаокно настроек. Первое, что эта штука делает – это находит ваши девайся или симуляторы:

Далее идет вопрос относительно того, как вы планируете подписывать приложения. Это, конечно, вопрос, т.к. я к моменту написания поста не был зарегистрирован ни на каких сайтах. Подсказка тут в том что это дело можно прокинуть, но я решил посмотреть… и вот самое важное, что должен видеть каждый:

А вот это очень правильно хотя… если учесть как у них там дела… хотя нет, дела у них не так плохо:

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

Интересно, зачем 2 часа на то чтобы выдать ключи? Вероятно там, на том конце, кто-то пробивает мое имя в Гугле чтобы узнать, являюсь ли я of good character. Между прочим, character checks все еще существуют в реале, например для того чтобы получить британский паспорт или права, за вас должен поручиться резидент. Но в данном случае это как-то особенно странно. Дайте мне мои ключи!!!

QNX Momentix IDE

Blackberry поставляется со своей собственной IDE для разработки под названием Momentix IDE. Это только звучит красиво. На самом деле – это Eclipse со всеми вытекающими. Какими? Ну во-первых, система не понимает свой собственный синтаксис:

Автодополнение мало того что выглядит неаппетитно, да еще и тормозит впридачу.

Редактирование UI происходит на языке QML который, не являясь XML-образным, вообще непонятно как редактировать. То есть выглядит это например так:

Автодополнения тут нет вообще (а мы еще ругаемся на XAML), но зато ошибку он подсвечивает. Конечно, этот же QML можно редактировать и в визуальном редакторе:

Все бы хорошо но вот незадача: контролы почему-то нельзя тащить на редактор, зато можно тащить на еще один тулбокс под названием Outline:

Эмулятор

Признаюсь: я все еще зол на то что Microsoft заставляет пользователей устанавливать Windows 8 только для того чтобы воспользоваться ролью Hyper-V (которая, пакость, еще требует обновленных процов, Core 4 Quad якобы не катит, нужен core i7) под которой запускать эмулятор. Слава Рогам что с Blackberry все проще – вам просто дается VM под VMWare, и все.

Первое что замечаешь — это варианты запуска OS для разных устройств. То что все это в одной виртуалке – это удобно, не люблю когда плодят сущности. Что не очень радует так это наличие Safe Mode, что как бы намекает что «что-то может пойти не так». (И действительно, на практике у меня система после перезапуска просто показала черное окно без каких-либо намеков на активность.)

Должен признаться — QNX Neutrino, подпиленный под Blackberry, выглядит неплохо:

Конечно, UI этот не для фэнбоев Apple а скорее для людей которые просто хотят to get shit done, поэтому собственно мне Q10 и нравится (цена не нравится совсем, правда).

Ну да ладно, вся суть в том, что имея приложение в Momentix IDE, можно нехитрой манипуляцией запустить его в эмуляторе. К сожалению, при первом запуске я получил лишь:

Поначалу, до того как я добавил ключи (которых ждал 2 часа), не работало вообще ничего, я потерял много времени чтобы все полетело. Но потом все запустилось и я был очень рад увидеть свое (пусть и унылое) приложение:

К слову скажу, что и в симуляторе и в QNX IDE я словил массу багов, один раз переустановил симулятор полностью (не советую его перезапускать) а также потратил лишний час-два на вещи которые просто отказались интуитивно работать.

Пишем Код

У меня обычно первое желание в «плюсовом» проекте – подключить STL и Boost. Но перед этим хочется показать как вообще все происходит. Например, вот как можно создать новый класс:

Добавил #include <vector>, скомпилировал, вроде съело. Это значит что STL, как минимум, работает. Добавляю метод и тут оказывается что IDE добавляет в автодополнение все что вообще только можно:

Это же как сильно нужно ненавидеть разработчиков чтобы в список дополнения залить вообще все, что только можно найти на верхнем уровне. Самое интересное что что моей функции из заголовочного файла в списке вообще нет, что бред и сумасшествие. Да, что там, для member fields автодополнения тоже нет, есть разве что для this-> но дальше него дело не идет.

Решил сделать кнопку. Внезапно в начале ввода атрибута появился комплишн, уже что-то

Смарт-комплишна на этот enum (если это можно так назвать) конечно не было, увы. Кстати, точно так же как и в Visual Studio/XAML, тут есть визуальный редактор свойств, который наверняка может быть полезен:

Для обработки события кнопки можно писать код прямо в QML, что выглядит неидиоматично. Даже XAML не опускается до такого уровня:

Естественно, что поставить брейкпоинт в это место нельзя, т.к. это работает только в кодовых файлах. Кстати, для брейкпоинтов у меня не получилось даже потыкать в левый margin чтобы их ставить, пришлось лезть в меню. Только я запустил все это дело под дебагом, как…

Ну неужели так сложно помочь пользователю синхронизовать все это дело? Почему я должен беспокоиться о том, что последние скачанные пакеты не стыкуются? Еще примечательно что, в отличии от Visual Studio (да и большинства вменяемых IDE), Momentix IDE не восстанавливает window layout из дебажного в обычный после того как вы закрыли отладку. Как холодной рыбой по лицу, чесслово.

Еще интересно то, что дебаггер у меня не сработал где просили но зато сработал где не просили, а именно почему-то воткнулся в main() хотя моего брейкпоинта там не стояло.

Да, и что касается Boost, то нашел на просторах интернета проект, который как раз портирует Boost для Blackberry, так что если что, можно воспользоваться.

Заключение

Понятное дело что для того чтобы понять как все действительно хорошо нужно попользоваться системой месяц-другой. Мое субъективно ощущение что все могло бы быть намного хуже, и что надо благодарить Рога за то, что у Blackberry получилось хоть что-то. По крайней мере, Cascades — неплохая система, и я думаю что Q10 – неплохой девайс.

Другое дело что API сделанный на С++/QML полностью неинтуитивен. Например, вот как выглядит код создания UI из QML. Попробуйте догадаться что тут что.

Так что против BBRY тут два фактора: неудобный язык (все же Java или C# поудобнее будут), а также неудобная IDE. Много ли разработчиков захотят писать под эту платформу? Пока что относительно пустой магазин Blackberry World — плохо для экосистемы, но что если ее забросают недопроектами в стиле тех «соревнований» которые устраивает Microsoft? Этого-то точно не хотелось бы. Лучше мало, но качественно, чем 99% мусора.

У меня пока всё. Comments welcome.

Written by Dmitri

5 мая 2013 at 11:47

Опубликовано в IDE

Tagged with , , , , ,

Нужен ли математикам статический анализ?

10 комментариев

Есть такая профессия, в которой люди на 50% процентов занимаются программированием, но при этом сидят на порой весьма “несовременных” технологиях, пишут в основном на С++, и никаких особых бенефитов от IDE вообще не имеют. И при этом не жалуются. В этом посте – про то, кто эти люди, чем они заниматся и чем мы можем сделать их жизнь лучше.

Знакомьтесь – квонты

Квонт, он же quant (quantitative analyst – численный, так сказать, аналитик) — это человек, который занимается применением математики в финансах, часто – для создания торговых систем, т.е. программ, которые занимаются автоматизированной торговлей на бирже. Эти люди – чаще всего магистры или PhD в области физики (да-да) или математики (в частности, с дипломами вроде MFE – Masters in Financial Engineering), в некоторых случаях, с дипломом CQF (один из очень небольшого кол-ва “неакадемических” дипломов) или даже с MBA Finance или а ля.

Квонты (я бы называл их квантами) программируют в очень узком ключе – их в большинстве случаев не волнуют новые тренды в Asp.Net или новых языках вроде Scala или D. Они живут в совершенно другой реальности, где доминантным маст-хэв языком был, есть и будет С++. Конечно, есть варинты (например, вспомним Jane Street и их любовь к OCaml), но суть остается одна – эти разработчики привыкли работать с достаточно ограниченным tool support, и не особо от этого страдают. Ведь согласитесь, для того чтобы эффективно выполнять математические рассчеты нужно лишь чтобы у вас худо-бедно работал intellisense, и то, это не так критично если нужно вызвать что-то вроде std::min().

Соответственно, текущее положение вещей наводит на мысль, что людям которые используют в основном C++ (а также MATLAB, VBS, R, ну и другие языки на вкус) tool support как бы не очень нужен.

И все же…

Приведу конкретный пример. На многих графических устройствах, операция a*x+b оптимизирована и проходит быстрее, чем умножение и сложение отдельно. Соответственно, когда я работаю с GPU.NET, у меня есть в R2P контекстное действие, которое превращает все, что похоже на “умножение-в-сложении” на вызов вроде DeviceMath.MultiplyAdd(a, x, b). Мелочь, а приятно, и программу ускоряет.

И это далеко не частный случай. Вот еще пример – допустим что вы попросили студента за еду реализовать вам ряд формул в коде, и студент в качестве реализации написал что-то вроде y = a * Math.Pow(x, 2.0) + b * x. Бред, не так ли?

И тут снова можно включить “электронный мозг” статического анализатора и начать помогать несчастному разработчику. Для начала ему нужно объяснить, что считать ряды Тэйлора в подобном случае раз, эээ, в 50 медленнее чем сделать x*x. Но даже объяснив это, вы получите вот такой результат:

y = a * x * x + b * x;

Это тоже не очень-то эффективно. Ведь тут целых 3 умножения, хотя можно обойтись двумя:

y = x * (a * x + b);

Опаньки, а это факторизация, задачка уже посложнее, особенно в контексте статического анализа. Тем не менее, она решабельна, и позволит квонту хоть немного но ускорить вычисления.

А это имеет смысл?

Действительно, кому нужна скорость? Ну, наблюдения показывают что ряд задач (например, Монте-Карло симуляции) очень любят кушать CPU. Микрооптимизация вычислений – это как раз то, что позволит аналитику протестировать свою стратегию быстрее, на большем объеме данных.

Еще одна полезная, хотя и тривиальная вещь – это рефакторинги с уклонов в сторону параллелизации – например рефакторинг for и foreach циклов в параллельные с сохранением правильной семантики. То есть, если разработчик написал

int [] elems = new int[] { ⋮ };
int sum = 0;
foreach (var e in elems) sum += e;

то почему бы нам не дать ему возможность отрефакторить этот цикл в параллельный:

Parallel.ForEach(elems, () => 0, 
                 (n, loopState, localSum) => 
                 {
                   localSum += n;
                   return localSum;
                 },
                 localSum => Interlocked.Add(ref sum, localSum));

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

Другие языки?

Я привел пример поддержки C#, т.к. не знаю доступной инфраструктуры для написания рефакторингов для С++ или (это было бы еще интересней) для CUDA C. Было бы интересно услышать мнение читателей насчет применимости всех этих идей.

Written by Dmitri

18 августа 2011 at 22:22

Опубликовано в .NET

Tagged with , ,