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

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

Задача про 2008

Этот пост — репост одного моего поста который был на Hexlet до того как Hexlet решил изобрести себя снова и сдвинул мой курс в «песочницу». Он немного технический, и я не хочу его терять. Надеюсь вы поймете. К сожалению, комментарии оригинального поста потеряны, но с этим ничего не поделать.

Недавно мы с коллегой ехали на очередной эвент, и он задал мне простую, казалось бы, загадку:

Как с помощью 13 (тринадцати) нулей и простых математических операций получить число 2008?

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

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

Вещественность

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

Самое очевидное решение — это 0^0 = 1 (это кстати спорно и вызвало много дебатов), но это в результате дает нам \left \lfloor{\frac{13}{2}}\right \rfloor = 6 нулей из которых, как вы понимаете, 2008 никаким образом не сделать. Должны ведь быть и другие варианты? Попробуйте угадать, какой оператор делает из нуля единицу?

Конечно же это факториал. Факториал — это обычно произведение всех чисел от 1 до n, так что например 3!=3\times2\times1, но у факториала есть одна особенность: 0!=1, а следовательно у нас теперь 13 единиц, а с ними уже можно работать. И кстати, пока не поздно, обращу внимание на то, что в условии задачи я упомянул простые математические операции. Если бы можно было использовать, например, логарифмы, то задача уже решена т.к. любое натуральное число можно выразить через логарифмы, корни, и три двойки (доказательство), а соответственно решение нашей задачи выглядело бы вот так:

\displaystyle 2008 = - \log_2\left(\log_2\left(\underbrace{\sqrt{\sqrt{...\sqrt{2}}}}_{2008}\right)\right)

Для решения выше потребовалось бы всего шесть нулей (т.к. 2 = 0! + 0!) а также 2008 квадратных корней но, как я уже сказал, хочется получить решение с помощью известных операторов, не привлекая в решение всякие сложные функции. Корни, возведение в степень, факториалы — это все ок.

Возведение в степень

Итак, нужно получить 2008. Самое очевидное — это найти близлежащее число 2^n. В данном случае, это 2^{11} и соответственно мы можем подобраться к решению вот так:

\displaystyle \begin{aligned}  2008 &= 2^{11} - 40 \\  &= 2^{2^3+3} - 5\times2^3  \end{aligned}

К сожалению, числа 2, 3 и 5 идут «по себестоимости» (т.е. чтобы сделать 5 нужно 5 нулей), и соответственно в 13 нулей мы ну никак не уложимся: для решения выше нужно аж 20 нулей!

Соответственно нашей первоочередной задачей становится поиск более «дешевых» чисел, и тут на помощь приходят…

Факториалы

Факториалы дают нам не только единицы с которыми можно работать, но также дешевые крупные числа. Например, 3!=6 а это значит что за три нуля мы можем получить не только 3 но также 6=3!, 720=(3!)!, и так далее. Если форма записи X_Y обозначает что число X требует для записи Y нулей, то мы получим следующие значения:

\displaystyle \begin{aligned}  0_1 =& 0 \\  1_1 =& 0! \\  2_2 =& 0! + 0! \\  3_3 =& 0! + 0! + 0! \\  4_4 =& 3 + 0! = 2^2 \\  5_4 =& 3! - 0! \\  6_3 =& 3! \\  7_4 =& 3! + 1 \\  8_5 =& 3! + 0! + 0! = 2^3 \\  9_5 =& 3^2  \end{aligned}

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

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

\displaystyle \begin{aligned}  2008 &= 2^3 \times 251 \\  &= 2^3 \times (216+36-1) \\  &= 2^3 \times (6^3 + 6^2 - 1) \\  &= \underbrace{2^3 \times \left(6^2(6+1)-1)\right)}_{14}  \end{aligned}

Увы, решение выше требует на один ноль больше, чем у нас имеется.

Другой подход к решению задачи — это разложить на множители не 2008 а, скажем 2008\pm10. Вот что нам дает MATLAB:

1998=2   3   3   3  37
1999=1999
2000=2  2  2  2  5  5  5
2001=3  23  29
2002=2   7  11  13
2003=2003
2004=2    2    3  167
2005=5  401
2006=2  17  59
2007=3    3  223
2008=2    2    2  251
2009=7   7  41
2010=2   3   5  67
2011=2011
2012=2    2  503
2013=3  11  61
2014=2  19  53
2015=5  13  31
2016=2  2  2  2  2  3  3  7
2017=2017
2018=2  1009

Из интересных чисел тут, пожалуй, 2000 и 2016. Также соблазнительно число 1998 (у него есть множитель 37=3!^2+1). Возьмем сначала 2000:

\displaystyle \begin{aligned}  2000 =& \underbrace{2^4\times5^3}_{12} \\  =& \underbrace{2\times\left(2^{3}+2\right)^3}_{11}  \end{aligned}

Это решение нам явно не подходит т.к. 2008 = 2000+8_5 т.е. для решения нужно 16 единиц. Попробуем 2016:

\displaystyle \begin{aligned}  2016 =& \underbrace{2^3\times6^2\times7}_{14} \\  =& \underbrace{6^4+(3!)!}_{10}  \end{aligned}

Но даже так, у нас остается три нуля, а 2008=2016-8_5, то есть для этого решения нам всяко не хватает двух нулей. Что же, подход с обычными (намек!) факториалами почти себя исчерпал, давайте еще попробуем что-нибудь сделать с числом 1998:

\displaystyle \begin{aligned}  1998 &= \underbrace{3!^2\times\left(3!^2+1\right)}_{11}  \end{aligned}

Это достаточно бессмысленное занятие т.к. двумя нулями лишние 10 не набрать, но, по крайней мере, мы выловили тот факт, что возможно стоит попробовать 3!^4:

\displaystyle \begin{aligned}  2008 &= 3!^4 + 712 \\  &= \underbrace{3!^4 + (3!)! - 2^3}_{15}  \end{aligned}

Увы и ах. Как я уже говорил в самом начале, число 2008 выбрано неслучайно — это число вставляет максимальное число палок в колеса. Нужен какой-то трюк чтобы снизить «стоимость» простых чисел вроде 8. К счастью, такой трюк имеется, и это…

Двойной факториал

Двойной факториал n!! — это тоже произведение всех чисел от 1 до n, но с шагом 2, т.к. например 5!!=5\times3\times1=15. Само по себе это кардинально меняет картину, и я хочу обратить ваше внимание на два равенства:

  • 8_4 = 4!!, то есть восьмерка стала «дешевле» на один ноль.

  • 48_3=(3!)!!

Итак, делим 2008 на 48 и получаем 41.8(3), а поскольку 42=6\times7, мы наконец-то можем попробовать получить ответ:

\displaystyle \begin{aligned}  2008 &= 48\times42-8 \\  &= \underbrace{(3!)!!\times\left(3!\times(3!+1)\right)-4!!}_{14}  \end{aligned}

Но что-то тут не так: 42 нам далось адским трудом, мы заплатили за него аж 7 нулей. На один ноль поменьше и все получится. На самом же деле, 42=48-6=(3!)!!-3! и вот, ура, у нас готово первое решение:

\displaystyle 2008 = \underbrace{(3!)!!\times\left((3!)!!-3!\right)-4!!}_{13}

Субфакториал

Разных факториалов бывает много и субфакториалы — это такой особый тип факториала, который определяет количество беспорядков порядка n. Высчитывается он так

\displaystyle !n=n!\sum_{k=0}^n \frac{\left(-1\right)^k}{k!}

Прелесть этого факториала в том, что он в очередной раз дает нам другие равенства, например 265_3 = !(3!). А это в свою очередь заставляет нас в очередной раз посмотреть на разложение 2008 на множители:

\displaystyle \begin{aligned}  2008 &= 4!! \times 251 \\  &= 4!! \times (!(3!) - 14) \\  &= \underbrace{4!! \times \left(!(3!)-5!!+1\right)}_{13}  \end{aligned}

Оптимизация задачи

Кому-то может показаться, что 13 нулей — это предел мечтания, но нет — вот например пара решений, где используются только 12 нулей:

\displaystyle \begin{aligned}  2008 &= \underbrace{(!5)^2 + \sqrt{\left((3!)!+1\right)} + 1}_{12} \\  &= \underbrace{\left(!(4!!)-1\right)\times2+5!}_{12}  \end{aligned}

Предлагаю игру в стиле code golf — оставляйте в комментариях решения, которые используют 11 нулей и меньше, посмотрим чье кунг‑фу лучше. No cheating! ■

Written by Dmitri

15 февраля 2016 at 0:39

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

Tagged with

Runtime Compiled C++

Нынче грех жаловаться на компиляцию. Я вот смотрю на то, что сейчас возможно в последней версии MSVC — там по сути компиляция инкрементальна не на уровне файлов (де компилим только те файлы, которые поменялись), а на уровне функций — остальное берется из кэша. Как оно точно работает я не знаю, знаю лишь что при изменениях я вижу нечто вроде

166 of 3863 functions ( 4.3%) were compiled, the rest were copied from previous compilation.
4 functions were new in current compilation
335 functions had inline decision re-evaluated but remain unchanged

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

Вообщем, рассказ-то у меня не про это, а про runtime-компиляцию, про которую я уже часто говорил под термином ‘динамическое прототипирование’ — вот тут у меня .NET-ное видео есть, можно посмотреть как это делать на C#-е.

Вообще этот подход хорошо иллюстрирует зачем вообще нужен Dependency Inversion Principle — да-да, именно inversion а не injection. Если попытаться объяснить этот принцип словами, получится что-то вроде этого:

Делайте Extract Interface на любом классе, по поводу и без повода. Замените все типы с Foo на IFoo. Если кому че не нравятся, пусть пишут официальную жалобу, в дубликате и с ксерокопией паспорта.

Вот как-то так. А вы думали, зачем Extract Interface? Конечно чтобы в рантайме заменить один тип на другой. Как? Объясняю:

  • Вы сделали объект типа Person.

  • В рантайме вы поменяли объект типа Person, скомпилировав еще один такой же но с пофикшенной багой.

  • Теперь у вас два объекта типа Person, но это разные типы!

Никакого вам duck typing’а! Следовательно, вы в принципе не можете зависеть от Person, а только от некого интерфейса IPerson который, кстати, менять нельзя если только вы не абстрагировались еще выше (но боюсь что это непрактино в 99% ситуаций).

А что в C++?

Да по сути то же самое, только еще проще т.к. ничего не надо делать, умные дяди уже все сделали. Естественно, что этот подход сугубо intrusive, т.к. это «плюсы» и тут нету reflection и иже с ним.

  • Файлы которые нужно подменить помечаются специальным макросом REGISTERCLASS.

  • В момент запуска вашей проги, система строит табличку того какой класс где определен.

  • Классы, которые хочется менять в рантайме есессно не положено инстанцировать «в лоб», так что

    IObjectConstructor* pCtor = m_pRuntimeObjectSystem->GetObjectFactorySystem()->GetConstructor( "RuntimeObject01" );
    if (pCtor)
    {
    	IObject* pObj = pCtor->Construct();
    	pObj->GetInterface( &m_pUpdateable );
    	if( 0 == m_pUpdateable )
    	{
    		delete pObj;
    		m_pCompilerLogger->LogError("Error - no updateable interface found\n");
    		return false;
    	}
    	m_ObjectId = pObj->GetObjectId();
    }
    

На этом этапе у кого-то может политься кровь из глаз, но я напоминаю, что это C++. Выше происходит следующее: мы через нашу runtime систему находим фабрику которая инстанцирует объект и вызываем конструктор, а потом пытаемся подкрутить его к интерфейсу — если получилось, сохраняем также id объекта чтобы его потом можно было правильно удалить.

Значит теперь к объекту мы можем достучаться только через его интерфейс. Интерфейс — это просто структура с pure virtual функциями, наподобии вот этой:

struct IUpdateable : public IObject
{
  virtual void Update(float deltaTime) = 0;
};

IObject — это важный базовый класс инфраструктуры. Ну так вот, что там дальше?

  • После запуска, система начинает мониторить все изменения. Если файл поменялся, этот файл (а также все его зависимости) собирается в отдельную динамическую либу.

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

  • Внешние компоненты, которые сами по себе не являются runtime-modifiable могут отлавливать события когда меняется структура изменяемых объектов и как-то на это реагировать.

Вообщем, все это очень интересно и, резюмируя, является еще одним козырем в рукаве, наряду с Cling, обертыванием C++ либ в Питон, и прочими извращениями. Enjoy!

Written by Dmitri

2 февраля 2016 at 2:01

Опубликовано в С++

Tagged with

Заметки про Rust

Содержание

Я не знаю как с экономической точки зрения в России можно не работать треть месяца, но ладно — для многих водка решает, а тех кому нечем заняться ждет обзор языка Rust с точки зрения человека который пишет в основном на C++/C# (хотя я теперь модный и знаю сколько-то Kotlin’а, что тоже полезно, т.к. Java трогать ну совсем не хочется).

Так вот, давайте по порядку и с заголовками, чтобы красиво было. (Да, подсветка синтаксиса тут во F#-у, т.к. wordpress.com пока не разродился на Rust.)

Примитивные типы

Начнем с целочисленных типов: тут все хорошо и стандартизированно: вместо всяких unsigned long long которые в тех же плюсах вообще непонятно что значат, в Rust тип u8 значит ‘unsigned и 8 битов’ что очень понятно, в принципе, ну и по аналогии i32 это 32-битный int.

Есть также usize/isize – это биты размера системы, то есть если вы на 64-бит (я надеюсь), и что хорошо, так это то что в отличии от C++, где есть весьма существенные непонятки с типом size_t, а также языками C++/C#/Java, где индекс массива может быть signed, в Rust-е индекс массива это строго usize. Это же здорово!

Да, насчет чисел с плавающей точкой — тут f32/f64, все просто.

Операторы

Все как в С-образных языках, но жестко выпилены операторы ++/--, а логические операторы можно применять только к булевым переменным и, наоборот, побитовые – только к целочисленным (то есть к f32/64 не получится). Это хорошо т.к. снижает кол-во возможных непоняток.

Переменные

Объявление переменной в Rust несложно, например:

let mut a:i32 = 2+3*4;

Тип, как видите, идет после названия. В примере выше он не нужен т.к. type inference в Rust очень умен и умеет делать вывод на этапе вызова (я позже покажу какого это).

Переменные в Rust — хитрые твари потому что:

  • Примитивы выделаются на стэке если только ты их вручную не задвинешь в кучу через Box<>
  • By default немутабельны, но могут быть сделаны таковыми с помощью ключевого слова mut
  • Владеют той памятью на которую ссылаются. Об этом позже т.к. идея сложная

Помимо локальных есть еще глобальные переменные:

  • Константы которые просто инлайнятся и сами адреса не имеют
    const MEANING_OF_LIFE:u8 = 42;
    
  • Статические переменные, которые можно даже делать мутабельными, но если ты так сделал – то придется ващевеськод оборачивать в unsafe т.к. система не может мониторить что ты правильно работаешь с глобальной переменной, в которую вообще все могут писать
    static mut Z:i32 = 123;
    

И да, язык хочет чтобы глобальные переменные были константами. И вообще, code style inspections встроены в сам компилятор что, имхо, дурной тон и так вообще не надо делать.

Control flow

If

Тут много интересного. Во-первых, нет тернарного оператора ?: — всесто этого if возвращает значение:

let day = if temp > 20 {"sunny"} else {"cloudy"};

Где return, спросите вы? А вот… в старой доброй традиции терминального ввода, MATLAB’а и прочих, правило Rust такое – если после чего-то нет точки с запятой ;, то это – return value. По мне так достаточно косячно т.к. ну очень плохо читается. Вариант использовать return тоже есть, конечно, но уверен что растаманы будут ругаться.

Да, эти if-ы всегда требуют фигурные скобки вокруг блоков, но зато не требуют круглых скобок вокруг проверки условия.

While/loop

Что касается while, то тут все то же самое, continue и break работают. А вот do-while не сделали, зато сделали loop который чем-то аналогичен while(true) или for(;;). И это в языке который печется о безопасности.

For

Цикл for работает как-то вот так:

for x in 1..11 
{
  // skip 3
  if (x == 3) { continue; }
  // stop at 7
  if (x == 8) { break; }
  println!("x = {}", x);
}

Как вы догадались, 1..11 это range, то есть набор чисел от 1 до 10 включительно. Такой вот «обходной» подход к for сравним с обычным IEnumerable, то есть ничего особенного, просто очередная редукция энтропии. Кстати, 11..1 не получится и приведет к косякам, ну и шаг хождения (как 1:101:2 в MATLAB) тоже указать нельзя.

А да, и если нужен индекс итерируемого элемента, есть специальный метод который вернет вам не просто элемент, а кортеж индекс-элемент:

for (pos,y) in (30..41).enumerate()
{
  println!("{}: {}", pos, y);
}

Match

Теперь насчет switch — его нет, есть match, и он раз в 100 мощнее. Ну вот например он умеет обрабатывать range’ы:

let country_code = 999;
let country = match country_code 
{
  44 => "UK",
  46 => "Sweden",
  7 => "Russia",
  1...1000 => "unknown",
  _ => "invalid" // try commenting this out - must cover all cases!
};

И да, он тоже, как и if возвращает значение, которое можно присвоить. Только вот тут есть один косяк… приглядитесь! Видете диапазон 1...1000? Там три точки. А ранее было две! И ранее 1..11 означало от 1 до 10 включительно, а тут — от 1 до 1000 включительно. Такой вот когнитивный диссонанс. И причем авторы языка это специально сделали, «чтобы люди не путались». Ну-ну.

Структуры данных

Массивы

Начнем с массивов. Ну вот как-то так:

let mut a/*:[i32;5]*/ = [1,2,3,4,5];

Тип массива я, опять же, закомментил — вывод типов и тут работает на ура. Массив на стэке. Можно менять по индексу, при неправильном индексе получим панику. Есть метод len() для длины, т.е. ходить по массиву можно так:

let b = [1u16; 10];
for i in 0..b.len()
{
  println!("{}", b[i]);
}

Не знаю как вам, а мне форма записи 0..b.len() как-то не очень. Зато синтаксис выше — [1u16; 10] — это для заполнения десяти элементов массива одним и тем же значением. Удобно.

Многомерные массивы тоже реальны как массивы массивов:

let mtx:[[f32; 3]; 2] = 
[
  [1.0, 0.0, 0.0],
  [0.0, 2.0, 0.0]
];
println!("{:?}", mtx);

Слайсы

Слайсы – это как бы ссылки на куски массива, т.е. диапазоны памяти. То есть, если у нас есть массив, можно его кусок одолжить и че-то с ним поделать:

fn use_slice(slice: &mut [i32])
{
  println!("first elem is {}, len = {}", slice[0], slice.len());
  slice[0] = 4321;
  // will crash
  //let z = slice[10];
}
fn slices()
{
  // a slice is part of an array
  // its size is not known at compile time
  let mut data = [1,2,3,4,5];
  
  // start w/o mut, borrow as a slice
  use_slice(&mut data[1..4]);
  use_slice(&mut data); // entire array
  println!("data after slice use = {:?}", data);
}

В отличии от массивов, слайсы могут быть хз какого размера.

Вектора

Вектор — дженерик тип Vec — это динамический массив, причем в куче. Можно добавлять и удалять данные.

let mut a = Vec::new();
a.push(123);
println!("a = {:?}", a);
let idx:usize = 0;
println!("a[0] = {}", a[idx]);

Из кода выше должен возникнуть резонный вопрос: где название типа данных? Почему мы можем вообще вызывать Vec::new() если нигде нет аннотаций типа? Ответ на этот вопрос – очень умный вывод типов Rust’а.

Да, поскольку в Rust есть Option<T>, доступ к элементам безопасен и может быть сделан вот так:

match a.get(5)
{
  Some(x) => println!("a[5] = {}", x),
  None => println!("error, no such element")
}

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

Да, насчет итерации вектора, тут все просто:

for x in &a { println!("{}", x); }

Вот этот амперсанд & перед названием ветора перед операции означает «одолжить». То есть на момент итерации, вектор «одолжен». Да, и варианта менять вектор во время итерации тут нет, спасибо Rust’у с его безопасностью.

Что еще? Вектор ведет себя как стэк (т.е. имеет push/pop методы). Вот если нужно например его опустошить в обратном порядке, можно так:

while let Some(x) = a.pop()
{
  println!("{}", x);
}

Выше — немного магии. Как это у нас в while внезапно не true/false а Option<T>. А вооот! Rust поддерживает if let/while let, которые проверяют на None/Some<T>. Очень удобно!

Строки

Тут все хорошо и плохо одновременно. Хорошо потому что Rust гарантирует что строки это всегда валидные UTF-8 последовательности, а плохо потому что есть два типа строк — String и &str.

Начнем с &str — это т.н. ‘string slice’, но не в классическом смысле: слайс строки как Vec<u8> крайне бессмысленнен т.к. это байты Юникодной последовательности, а &str – это особный тип, который дает доступ к строке, которую можно разбить на последовательность буков и делать с ними что-то:

let s:&'static str = "hi there!";
// s = "bar"; // cannot reassign immutable
//let a = s[0]; // cannot index
  
for c in s.chars().rev() // reversed! also as_bytes()
{
  println!("{}", c);
}

В примере выше, мы статически аллоцируем текст и берем его как &str а дальше с помошью chars() разбираем на буквы и делаем что хотим.

А теперь про String — вот этот тип как раз для того чтобы менять, т.е. это мутабельный Vec<u8> в который можно аппендить и вообще:

let mut letters = String::new();
let mut a = 'a' as u8;
while a <= ('z' as u8)
{
  letters.push(a as char);
  letters.push_str(","); // note the _str
  a = a+1;
}
println!("{}", letters);

Вообщем, со строковыми типами в Rust много чехорды, люди пишут умные блог-посты на эту тему, и толком непонятно что юзать – зависит, конечно, от того будете ли вы вашу строчку «шарить».

Option<T>

Это специальная структура данных (аналог boost::optional или F#‘ному Option<'t>) которая является перечислением, и имеет два возможных значения

  • None — значит «данных нет» и тут нечего ловить.
  • Some(T) где T — это то значение что вернули.

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

let x = 3.0;
let y = 0.0;
let result:Option<f64> =
  if y != 0.0 { Some(x/y) } else { None };
match result {
  Some(z) => println!("{}/{}={}", x, y, z),
  None => println!("cannot divide {} by {}", x, y)
}

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

if let Some(z) = result { println!("result = {}", z); }

Кортежи

Не, ну а как же без них? Хотите вернуть из функции и сумму и произведение двух чисел? Пожалуста:

fn sum_and_product(x:i32, y:i32) -> (i32, i32)
{
  (x+y, x*y)
}

Теперь можно их выписать например вот так:

let x = 3;
let y = 4;
let sp = sum_and_product(3, 4);  
println!("sp = {:?}", sp);
println!("{0} + {1} = {2}, {0} * {1} = {3}", 
  x, y, sp.0, sp.1);

Не нравится индексиция через точку? Вы еще С++ не видели. Ну ладно, ладно, давайте тогда деструктурируем:

let (a, b) = sp;
println!("a = {}, b = {}", a, b);

Кортежи-кортежей тоже в принципе реально, но написать foo.1.2 вам никто не даст:

let sp2 = sum_and_product(4,7);
let combined = (sp, sp2);
println!("last element is {}", (combined.1).1); 

Зато деструктуризация кортежа-кортежей выглядит красиво:

let ((c,d),(e,f)) = combined;

Что еще: а, да, кортеж это ж где разные типы можно хранить. Вот, пожалуйста. И да, одноэлементные кортежи немного кривовато делаются:

let foo = (true, 42.0, -1i8);
let meanings = (42,);

Структуры

Старый добрый сишный struct, никакого буллшита:

struct Point
{
  x: f64,
  y: f64
}
struct Line
{
  start: Point,
  end: Point
}

Методы в его тело не добавляются, а конструкторов-деструкторов как таковых нет как феномен, инициализация идет как-то вот так:

let p = Point { x: 3.0, y: 4.0 };
println!("point p is at ({},{})", p.x, p.y);
let p2 = Point { x: 5.0, y: 10.0 };
let myline = Line { start: p, end: p2 };

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

Перечисления (enum-ы)

Старый добрый… а, что, не такой? А, ну да. Enum в понимании Rust – это совокупность просто лейблов, кортежей и struct’ов, типа используйте что хотите (и да, generic enums тоже возможны, смотрите на Option). Вот например как можно описать цвет:

enum Color
{
  Red,
  Green,
  Blue,
  RgbColor(u8,u8,u8),
  CmykColor{cyan:u8,magenta:u8,yellow:u8,black:u8},
}

Ну а для разбора такого счастья можно использовать всю мощь pattern-matching’а:

let c = Color::CmykColor{cyan: 0, magenta: 128, 
  yellow: 0, black: 255};
match c
{
  Color::Red => println!("r"),
  Color::Green => println!("g"),
  Color::Blue => println!("b"),
  Color::RgbColor(0,0,0) 
  | Color::CmykColor{black:255,..} 
    => println!("black"),
  Color::RgbColor(r,g,b) => println!("rgb({},{},{}", r, g, b),
  _ => ()
}

Заметили .. выше? Это не range, конечно, это описание того что «пофиг чему равны другие поля».

Функции

Тут все, вообщем-то, просто:

Просто функции

Функция начинается с fn, далее берет один или несколько аргументов (тип, как всегда, после имени):

fn print_value(x:i32)
{
  println!("value = {}", x);
}

Функции могут также возвращать значения: тип возврата пишется через стрелочку:

fn product(x: i32, y: i32) -> i32 // return value
{
  let z = x * y;
  z // no semicolons
}

Возврат, как уже говорил, идет того значения, у которого нет ;.

Аргумент можно передать «по ссылке» – для этого используется & как на типе аргумента так и в функции, а для «дереференса» ссылки используется *. То есть поведение совсем уж в разрез с С++.

fn increase(x: &mut i32) // start with i32
{
  *x += 1;
}
let mut z = 1;
increase(&mut z); // lend z

Методы

Помните я сказал, что у struct’ов как таковых нету собственных функций? Но их можно добавить! Делается это вот так:

struct Point
{
  x: f64,
  y: f64
}
struct Line
{
  start: Point,
  end: Point
}
impl Line
{
  fn len(&self) -> f64
  {
    let dx = self.start.x - self.end.x;
    let dy = self.start.y - self.end.y;
    (dx*dx+dy*dy).sqrt()
  }
}

Ключевое слово impl позволяет определить реализацию того или иного метода для структуры. Заметьте как, выше, функция sqrt() вызывается на типе f64! Хотя я конечно же мог бы вызвать ее как f64::sqrt(), тут как кому удобнее.

Замыкания

Ну вот есть у вас функция:

fn say_hello() { println!("hello"); }

Вы можете взять и сделать из нее переменную. И потом вызвать ее как функцию:

let sh = say_hello;
sh();

Это никого не должно удивлять. Как и вариант создания таких переменных прямо в месте где хочется их использовать. Тут у нас Ruby синтаксис! Серьезно, вот:

let plus_one = |x:i32| -> i32 { x + 1 };
let a = 6;
println!("{} + 1 = {}", a, plus_one(a));

То есть plus_one это вполне себе функция. Сразу вопрос: а что с захватом окружения? А вот тут включается весь злой арсенал проверок Rust: если что-то из окружения захвачено, мы не отпустим пока сама лямбдочка не будет удалена. Как? А вот так:

let mut two = 2;
{
  let plus_two = |x|
  {
    let mut z = x;
    z += two;
    z
  };
  println!("{} + 2 = {}", 3, plus_two(3));
}
let borrow_two = &mut two;

Выше — искусственно сделанный scope, который гарантирует что plus_two будет удалена и, тем самым, «отпустит» two, которую она захватила.

Вот как-то так: передали контроль над переменной: все, пиши пропало. Поэтому лучше одалживать.

Время жизни

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

Владение

Каждая переменная «владеет» контентом, но поведение отличается в зависимости от того, на стэке она или в куче. Вот если она в стеке, например

let v = vec![3,2,1];

то это значит что такое вот невинное, казалось бы, присваивание как

let v2 = v;

означает фактически, что v2 забирает управление памятью, и что отныне переменную v использовать нельзя, т.к. у нее «забрали» управление. Это значит что вот это не скомпилируется:

println!("{:?}", v);

То же самое происходит если лямбду написать как

let foo = |v:Vec<i32>| ();
foo(v);

Поскольку функция foo забрала вектор, использовать его пока лямбда в scope — нельзя.

Это поведение на языке C++ называется move semantics, т.е. объект как бы «перемещается», а старая его копия больше не валидна. Работает это, правда, только для тех типов которые не определили трейт Copy (о трейтах позже). Для примитивов работает этот трейт, и у нас копирование вместо move’а:

let u = 1;
let u2 = u;
println!("u = {}", u); // компилится без проблем!

Как вернуть управление из функции? Ну, можно вот так:

let print_vector = |x:Vec<i32>| -> Vec<i32>
{
  println!("{:?}", x);
  x
};

Но это гемор, поэтому Rust вводит такое понятие как…

Одалживание (borrowing)

Итак, мы с вами договорились до того, что в любой момент только одна переменная «владеет» доступом к данным, а все другие — в пролёте. В терминах C++, можно сказать, что передача такого параметра — это передача unique_ptr.

Что делать если хочется просто поработать над объектом (т.е. shared_ptr)? Тогда можно передать ссылке на него, и эта ссылка означает «одалживание» (borrowing) объекта:

let print_vector = |x:&Vec<i32>| // take a reference
{
  println!("x[0] = {}", x[0]);
};
let v = vec![3,2,1];
print_vector(&v);
println!("v[0] = {}", v[0]);

Также эту ссылку можно сделать, в принципе, мутабельной и менять объект.

Lifetime

Вы не видите lifetime’ов точно так же, как на выводе типов вы не пишете вручную аннотаций. А они есть. Например, типичная функция выглядит вот так:

fn bar<'a>(x: &'a mut i32) -> &mut i32 // lifetime elision
{
  x
}

Вот это вот <'a> может показаться type parameter’ом из F#, но нееет, это время жизни, и оно подчиняется разным хитрым правилом. Есть время жизни 'static, которое обозначает жизнь программы. Другие определения — это на ваше усмотрение.

Про lifetime’ы можно написать отдельный пост (или несколько). Это очень крутая штука, но она вирусная (как GPL) и способна просочиться через весь ваш код.

Всякая всячина

Крейты (ящики)

Rust поставляется с хитрой системой сродни NuGet’у. Проектным файлом служит файл в формате TOML, который описывает не только то, что мы строим (не беспокойтесь, файлы руками описывать не нужно), а также зависимости которые взяты из репозитария crates.io или откуда-то ещё.

Соответственно, помимо компилятора, есть еще crate.exe, который может как собрать ваш проект, так и запустить его или, например протестировать.

Модули

В Rust все делится на модули (ключевое слово mod) которые можно либо держать в одном файле либо распихать по файлам и папкам. Вот например тут

pub mod greetings
{
  pub mod english;
  pub mod french
  {
    pub fn hello() -> String { return "bonjour".to_string(); }
    pub fn goodbye() -> String { return "au revoir".to_string(); }
  }
}

описан файл lib.rs (значит будет собрана DLLка), и хоть модуль french включен прямо тут, модуль english лежит в файле greeting/english.rs. Convention over configuration, однако!

Ключевое слово pub определяет, что видно наружу, а что нет. Импортируется и ипользуется это очень просто:

extern crate phrases;
use phrases::greetings::french;
fn main() {
    println!("English: {}, {}", 
      phrases::greetings::english::hello(), 
      phrases::greetings::english::goodbye()
    );
    println!("French: {}, {}", 
      french::hello(), 
      french::goodbye()
    );
}

Ключевое слово use — что-то вроде using namespace в C++.

Да, забыл сказать — пакеты компилируются на стороне пользователя, т.е. поставляются как сорцы. Упс!

Тестирование

Как и язык D, Rust просто влепил тестирование прямо в суть языка на уровне набора атрибутов. Вот например

#[cfg(test)]
mod tests
{
  extern crate phrases;
  #[test]
  #[should_panic]
  #[ignore]
  fn english_greeting_correct()
  {
    assert_eq!("helloo", phrases::greetings::english::hello());
  }
}

Делает конфигурацию test, добавляет тест который ожидает падения, ну а дальше все как и в других языках. Запускается это дело с помощью cargo test. Как и в D, это очень удобно, не надо качать сторонние фреймворки.

Документация

Специальные комментарии /// для кусков кода и //! для всего модуля целиком дают rustc.exe сгенерить красивую документацию по вашему коду. Highly recommended!

Заключение

Я тут постарался как-то описать Rust по сравнению с C#/C++. Не могу сказать что хочется вот прямо взять и начать на нем писать, но некоторые умные идеи я из него почерпнул. ■

Written by Dmitri

4 января 2016 at 23:02

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

Tagged with

Отсутствие итогов 2015 года

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

Прежде всего, причина по которой руки поднялись что-то писать, что в плане успешности год был офигенный. О-фи-ген-ный! Я имею ввиду с финансовой точки зрения, т.к. рассказывать про то как я написал ПО, которое изменило человечество, я не могу. Я уже давно пишу исключительно для себя, и боюсь в этом плане чего-то интересного я не подкину. При этом у меня все еще возникают интересные идеи в плане ПО, которыми я иногда делюсь.

Примерно на этом этапе я мог бы позлорадствовать про доллар за 74 и евро за 80, но скажу лишь, что когда на рынке идет боковичок и деньги в буквальном смысле раздают, надо брать. Тут и боты толком не нужны, берем механическую клаву, скальперский привод (или на коленке писаный самопал) и начинаем клацать.

Насчет гаджетов всяких и программулек. Ну что тут сказать? Я записал сколько-то видосов для SpbDotNet (.NET сообщества в Питере) на Sony α7Rii, это лучший фотоаппарат на сегодняшний день и еще долго таким будет. И вообще я много на него уже пофоткал и очень доволен, хотя нет предела совершенству.

Насчет языков программирования — на D в уходящем году выло написано ровно ничего. На F# — только фиксы в MathSharp по требованиям пользователей. Покупки прог в этом году шли прятным бонусом, Брайан Даунинг даже сделал обзор X2C у себя в видео, заодно простимулировал меня пофиксить несколько проблем с разным легаси вроде Excel функций про которые я вообще не знал.

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

Да, все-таки удалось попробовать Rust, но выводов пока нет. Я тяготею к тому что приносит деньги, так что мне сильно отъезжать от канонов негоже. К тому же, С++ радует, я посетил ряд конференций — это C++ Russia, ACCU, CppCon, Meeting C++ и на двух последних даже сделал доклады. Ну были и другие конфы, всего по мелочи.

Что ещё? Блин, все-таки хочется влиться в финмат тусовку по полной, но мы уже вошли в какую-то зону апатии, когда мы на поезде с которого уже не сойти, а последняя остановка называется retirement, причем не в смысле «пенсий» — у меня ее нет, и у вас скорее всего тоже — а скорее в смысле «лежать на пляже на канарах». Канары хорошие, да, годное место.

Чё-то как-то сумбурно получилось, и даже удивительно что вы дочитали досюда. Наверное в новом году нужно делать какой-то reboot и начать писать про финмат, правильное использование статистики, и прочее добро. Только дойдут ли у меня руки? Вот не уверен.

Вообщем… всех с новым 2016! Не знаю как вам, а мне кажется что все будет просто супер!

Written by Dmitri

31 декабря 2015 at 23:59

Опубликовано в Life, Technology

Tagged with

Монада Maybe на языке C++

Монада – это вообщем-то просто паттерн, но из функционального программирования. А поскольку в C++ есть и лямбды и всякие функциональные объекты, почему бы не построить монаду? Не обещаю что будет красиво, но все же.

Сценарий

Монада Maybe – это такой подход при обработке присутствия или отсутствия того или иного поля у класса. Вот например допустим мы моделируем человека и у него есть адрес:

struct Person
{
  Address* address = nullptr;
};

Сам по себе адрес – это там всякие номер дома, улица и так далее, но если вы мажор (или Enya) и вы купили замок, то у него вместо улицы и номера доме есть просто имя. Соответственно имеем:

struct Address
{
  string* house_name = nullptr;
};

Тут и выше, использование raw pointer’ов – оно намеренное. Можно было бы вместо них использовать shared_ptr или boost::optional, об этом поговорим позже.

Итак, допустим мы хотим написать функцию которая, если ей скормить указатель на Person, печатает имя дома если таковое имеется. Простая реализация будет выглядеть как-то так:

void print_house_name(Person* p)
{
  if (p!= nullptr && p->address != nullptr && p->address->house_name != nullptr)
    cout << *p->address->house_name << endl;
}

Думаю проблема ясна – все эти проверки на nullptr утомительны и хотелось бы че-то попроще. Ну как сказать попроще… поумнее что ли.

Реализуем Maybe

Итак, мы будем строить экскаватор, т.е. класс который умеет копать вглубь объектов и при этом проверки на nullptr в него встроены, т.е. их не нужно делать самому. Начнем с того, что сделаем просто класс с указателем:

template <typename T> struct Maybe
{
  T* context;
  explicit Maybe(T* const context)
    : context{context}
  {
  }
};

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

Person p;
Maybe m{p}; // не сработает 
            // придется писать Maybe<Person> m{p};

Это как-то уныло, в связи с чем мы реализуем helper function который займется собственно выводом типов:

template <typename T> Maybe<T> maybe(T* context)
{
  return Maybe<T>(context);
}

Немного грустно писать подобные вещи, но что поделать. Теперь мы можем написать Person p; maybe(p) а дальше вызывать функции на свежеиспеченном объекте. Что за функции? В вот тут все самое интересное.

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

maybe(p)
  .With([](auto x) { return x->address; })
  // а что тут - потом узнаете

Тут правила такие:

  • Если текущий контекст не nullptr, то можно исполнять лямбду, и возвращать Maybe<Address> с установленным контекстом
  • Если текущий контекст уже nullptr, то вызывать лямбду нельзя, но возвращать контекст все еще нужно

Печаль состоит в том, что нельзя просто вернуть текущий контекст, т.к. Maybe<Person> и Maybe<Address> – это разные типы. В связи с этим мы получим что-то вроде

template <typename Func>
auto With(Func evaluator)
{
  if (context == nullptr)
  {
    // а вот тут будет интересно
  }
  else
  {
    return maybe(evaluator(context));
  }
}

Итак, вроде как возврат значения в случае если «все ОК» идет через функцию, которая тут шаблонным параметром представлена (были попытки использовать std::function, но оно в контесте шаблонов с лямбдами не дружит). Так вот, тут все как бы ОК, но что насчет возврата контекста если у нас уже nullptr? По идее нужно вернуть всего лишь Maybe<T>{nullptr} где T – это тип контеста, который использует скормленная нам лямбда. Проблема в том, что

  • У нас нет T в явном виде
  • Зато мы знаем что функция, которую нам скормили, возвращает T*
  • Следовательно, удаляем указатель, и вперед:
  
template <typename Func>
auto With(Func evaluator)
{
  if (context == nullptr)
  {
    return Maybe<typename remove_pointer<decltype(evaluator(context))>::type>(nullptr);
  }
  else
  {
    return maybe(evaluator(context));
  }
}

Как видите, получилось немного адово, но оно работает. Теперь можно писать

maybe(p)
  .With([](auto x) { return x->address; })
  .With([](auto x) { return x->house_name; })

и углубление в структуру произойдет только если текущий контекст не nullptr.

Icing on the Cake

Обычно в монаду Maybe добавляют еще всякого мусора для просто обработки, if-ов и всякого такого. Например, добавим в нашу монаду функцию Do(), которая будет просто выполнять некую лямбду:

template <typename Func>
auto Do(Func action)
{
  if (context != nullptr) action(context);
  return *this;
}

Выше, как видите, мы как раз можем возвращать *this потому что контекст мы в этой функции не меняем. Теперь мы можем дописать нашу оригинальную функцию:

void print_house_name(Person* p)
{
  maybe(p)
    .With([](auto x) { return x->address; })
    .With([](auto x) { return x->house_name; })
    .Do([](auto x){ cout << *x << endl; });
}

Вот как-то так: теперь эту функцию можно саму вызывать с nullptr, или вызвать с не до конца инициализированным адресом (или отсутствующим вообще), и все будет работать спокойно и безопасно, просто как только цепочка вызовов наткнется на nullptr, все что будет происходить дальще — один большой NOOP.

Дискуссия

Я тут очень синтетично использовал nullable конструкты, что в реальной жизни используют разве что в языке С, в котором все равно подобную штуку не построить. В реальной жизни можно строки очень часто просто держатся by value, то есть ну будет пустая строка, но никак не null, и соответственно придется уже разбирать тот факт что она пустая.

То же самое насчет просто объектов, которые часто не T* а shared_ptr<T>/unique_ptr<T> или даже boost::optional<T> и соответственно проверять их уже нужно по-другому.

Вообщем вот как-то так. Спасибо коллегам из R++ за помощь с конструированием этой монстрятины. Да, с «методами расширения» да более краткими лямбдами было бы еще круче, я знаю! ■

Written by Dmitri

24 декабря 2015 at 14:01

Опубликовано в С++

Tagged with ,

Еще немного про ботов которые будут на DotNext

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

Так вот, о чем это я? Да, боты вообщем. Я на конфе расскажу как все это дело пишется, сейчас же хочу обсудить некоторые сценарии того, зачем собственно все это надо. Не все, конечно сценарии, но некую выборку. Итак, полетели!

Книги и прочая утварь

В 2008м году, Wiley (они публикуют, в том числе, много финансовых книг под лейблом Wiley Finance) подало в суд на некого Таиландца который посмел, скотина, продавать не-американские издания книг американцам. Wiley суд, к счастью, продули (но очень боролись!), а нам же предстоит разобраться в том, откуда ноги растут у этой проблемы.

Вы наверное знаете, что в США и в Европе цены на многие вещи, скажем так, отличаются. В большинстве случаев, если брать электронику, то она дешевле в США, чем лично я пользуюсь, т.к. периодически летаю на всякие конфы. В свое время, книги тоже были сильно дешевле в США чем в Европе, и еще, в отличии от других товаров, не подпадали под налоги (НДС, который может быть под 20%, плюс налог на импорт).

Но я вас удивлю, паблишеры — жадные сцуки. Точнее у них те же проблемы что и у record labels: народ все пиратит нещадно, таких наивных как я, которые любят «читать мертвое дерево» уже мало. В результате, паблишеры озверели, что привело к следующему:

  • Книги в США стали дороже чем в Европе.
  • Паблишеры начали делать разные «редакции» книг для США и International, помечая их большими красными лейблами «not for sale in the United States».
  • Умные поцики поняли что это целый рынок и начали перепродавать не-US книги в Штатах. Согласно first-sale доктрине, их деятельность признана полностью законной.

Паблишеры в свою очередь звереют в разных направлениях. Например, они публикуют книги в твердом переплете, делая их безумно дорогими для обычных читателей — сделано это специально, так чтобы книги покупали только библиотеки у которых, считается, «резиновый» бюджет. Ну а что касается журналов, то есть реально подоночные компании вроде Elzevier которые взвинчивают на них цены и продают не индивидуально а наборами по несколько штук, что порой приводит к бойкотам и прочей нечести.

Так вот, о чем это я? Ах да, есть такой феномен, что периодически некоторые книги сильно падают в цене. Раза в 2-3. Причем это очень сложно уловить. Например, если взять книгу Glasserman’а по методам монте-карло, то вы увидите что ее цена в твердом переплете (GBP35) меньше чем в мягком (GBP47). Почему такой перекос? И почему я, в свое время, смог купить ее где-то за GBP20?

Ответ на это простой: там боты. Цены на книги на Амазоне и не только выставляются динамически. Причем боты иногда перегибают палку, выставляя на книгу цену в USD23 миллиона например. Боты которые выставляют цены мы не можем контролировать, но мы можем их мониторить и, по возможности, находить возможности не только для удобного приобретения но и для перепродажи.

Вектора тут два:

  • Цена на книгу низкая сама по себе относительно исторических значений. Берем на Amazon сразу пятак, продаем на Abebooks, Ebay или где-то еще.
  • Цена на книгу существенно отличается от цены в другой стране. Тут тоже можно поарбитражить.

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

Самолеты и прочие средства передвижения

Ценообразование полетов – та еще песня. Мы знаем что там как-то все сезонно, плюс зависит от пипла который уже набился на ваш самолет, но строго говоря — все очень мутно, и боты эту мутность разруливают.

Работает это примерно так: вы допустим стали биологическим рабом, нарожали потомства и теперь не можете вообще выехать за иключением школьных каникул. Ну, хотя бы даты известны. Задаете даты, бюджет (ориентировочно), и сколько вы готовы лететь/куковать в аэропорту на пересадке. А дальше бот периодически (но не очень часто т.к. API платный) делает поиск и возвращает вам возможные результаты, оптимизируя по вашим показателям вроде «я никогда не полечу на RyanAir».

Дальше — интереснее. Если вы (не дай сотона) часто летаете, у вас наверняка накопилась куча «поинтов» которыми можно расплатиться вместо того чтобы тратить реальные физ.деньги. Боты – единственный способ понять, сколько ваш полет будет стоить именнов этих поинтах. Не будем забывать, что поинты можно докупать, так что возможности для оптимизации достаточно обширны.

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

Мероприятия в разрезе времени

Я часто где-то бываю, и обычно помимо «официальных» эвентов мне в перерывах нечем заняться. С другой стороны, есть прекрасные сайты вроде Meetup.com которые агрегируют разные встречи географически. И это уже здорово, т.к. Meetup можно мониторить и приходить на то что нравится — например, из последнего, я сделал доклад на Chicago C++ User Group пока был в Чикаго — только это правда не я нашел эвент, мои боты натренерованы на другого сорта эвенты.

Тут есть один нюанс: Meetup в какой-то момент ссучился и теперь не дает вступать в географически разные группы. Что как бы проблема если хочется общаться и региться на мероприятие. И тут… ну да, тут опять помогают боты чтобы зарегить себе 100500 разных аккаунтов (примечательно что и сам саппорт Meetup предлагает так делать) и потом гулять по сайту уже этими аккаунтами. Неудобно, да, но лучше так чем никак.

Еще более интересная задача — это event planning в разрезе. Откуда я знаю, что где и когда, и когда надо подавать заявки? В принципе тут можно сильно накосячить, а можно наоборот сфомировать очень эффективную поездку. Я не скажу что боты тут суперполезны (разве что попарсить RFP на Lanyard, хотя там и RSS есть), но им тоже можно найти применение. Но скорее всего, в этом случае, система – это большая такая записная книжка, см. например мой календарь.

Заключение

Вообщем, я тут немного прошелся по некоторым юз-кейсам, но не упомянул другие не менее интересные варианты. (например, создание чат-бота для сервиса анонимного чата дабы найти себе S.O.). Надо же что-то оставить на доклад (который будет тут). До встречи на конфе! ■

Written by Dmitri

17 ноября 2015 at 16:41

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

Анонс Guidelines Support Library на CppCon

Тут на CppCon, Бьярне (тот который язык С++ создал) анонсировал некий набор гайдлайнов по С++ а также либу для реализации некоторых из них.

Правила

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

NL.1: Don’t say in comments what can be clearly stated in code

Reason: Compilers do not read comments. Comments are less precise than code. Comments are not updates as consistently as code.

Example, bad:

auto x = m*v1 + vv; // multiply m with v1 and add the result to vv

Но дальше идет вообще нереальный перл в виде

Enforcement: Build an AI program that interprets colloquial English text and see if what is said could be better expressed in C++.

Что-о? Может еще сразу звездолет до Альфа Центавры построим, раз уж на то пошло? То есть, я что хочу сказать: совет хороший, да, но на практике никакой анализ подобного вида пока, скорее всего, невозможен. Кстати насчет enforcement, вот типичные советы про проверку на правила:

  • Not possible — вообще невозможно. Ну, зато честно.

  • Use a tool — воспользуйтесь тулой. При этом кто и как эту тулу напишет — не раскрывается.

  • Issue a diagnostic — выдать диагностическое сообщение. Это к компиляторостроителям, хотя и тулы тут могут помочь.

В некоторых случаях, еще предложены и фиксы, а вот это уже полезно т.к. тут не только диагностику можно делать (в стиле PVS studio) но и реально поправить прямо в IDE.

Guidelines Support Library (GSL)

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

array_view — тем кто знаком с C++ AMP это название знакомо. Это “вьюшка” поверх массива, которая позволяет, например, безболезненную 2D итерацию по 1D картинке (это ой как полезно). Также есть понятие index<>, т.е. умного итератора по вьюшкам, опять же по аналогии с C++ AMP:

#include <array_view.h>
#include <iostream>
using namespace Guide;
void array_view_demo()
{
  int data[100];
  int i = data[1000]; // bad
  array_view<int, 100> v(data);
  std::cout << v[1000] << std::endl; // fail_fast_assert
}

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

string_view — ну вы уже догадались, это вьюшка для C-образных строк, суть примерно та же. Есть еще вариации вроде cstring_view (константный вью), wstring_view, ну вы поняли… все это на практике тот же array_view с соответствующими возможностями.

Ассерты — тут их 2, Expects и Ensures (ну прям как в Code Contracts), оба они на практике просто бросают fail_fast_assert если условие не выполнено.

not_null<T> тоже конечно интересная идея, думаю все догадались — это фактически как референс, только суть референсов в том что их нельзя перебайндить, а вот такую хренотень — можно. Но конечно после C# аннотаций [NotNull] мне как-то очень сложно понять, зачем загрязнять тип. Хотя контраргумент тут в том, что если нужно передать вектор не-null значений, то на C# это вообще не понятно как обозначить, а тут все ясно — vector<not_null<T>>

Симметричной конструкцией тут является maybe_null и тут нужно пояснить, зачем это всё. Дело в том, что maybe_null например удаляет операторные указатели вроде ++ так чтобы было меньше шансов запороть указатель. Подразумевается, что этот указатель ссылается на один объект, а не на массив.

narrow_cast — тоже интересная идея. Суть в том что некоторые narrowing conversions мы все-таки готовы допускать, при условии что значение не меняется и остается тем же. Так вот, narrow_cast как бы именно это и делает, а в случае если значение меняется, он тупо кидает narrowing_error.

Ну и наконец owner<T> — это всего лишь заглушка (то есть на самом деле owner<T> это и есть T) которая поможет тулам статического анализа понимать, что происходит передача владением указателя. Бинарник эта штука никак вообще не меняет, и является всего лишь намеком, который можно проверить тулой и сделать ворнинг если чего не так.

Заключение

Вся эта тема подразумевается как OSS комьюнити-дривен фреймворк, всё на ГитХабе, можно делать PR и дискутировать. Либа сама по себе достаточно маленькая, header-only, включить ее в проект несложно. Как вы поняли, суть всего этого в том, чтобы дать разработчикам тулов в отсутствии полноценных метаданных некоторые дополнительные хинты по тому, что код на самом деле делает. Удачи!

Written by Dmitri

21 сентября 2015 at 23:28

Опубликовано в С++

Tagged with ,

Отслеживать

Настройте получение новых записей по электронной почте.

Присоединиться к ещё 139 подписчикам