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

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

Отзыв о конференции DotNext Piter 2018

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

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

Первое что скажу — 12 спонсоров в этот раз, это круто, вроде в 1й присест был JetBrains один и то бесплатно, а тут 12. Правда спонсоры это в основном сервисные компании, которые платят копейки (копейки в моем понимании это <$1kk/год) — EPAM, DataArt (черт, я там работал целых аж 3 месяца), и прочие подобные конторы. Из продуктовых там были JetBrains, Fast Reports, хз не помню кто еще. Главным спонсором был Альфабанк, для меня фраза “ритейловый банк” синоним “раковая опухоль”, да не будем о плохом, товарищи. Короче я понял что я unemployable :)

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

Apps, algorithms and abstractions: decoding our digital world, Dylan Beattie: давайте начнем с того что SkillsMatter это какбы контора которая, как и я, зарабатывает деньги. Что они там делают — ну, тренинги делают. А сам по себе доклад был 100% популистский, что хорошо, т.к. конфа должна начинаться и заканчиваться чем-то легким. Поэтому было вообще ОК, Дилан рассказал про то сколько всего сложного происходит в тот момент когда ты получаешь на мобильнике картинку котика и пишешь LOL. Отдельный ему респект за русскоязычный юмор (щи да каша пища наша!), очень хорошо подготовился. Почему правда англичанин одевается как техасский ковбой история умалчивает, наверное это часть его рок-имиджа — это ведь он (и Вагиф Абилов, кстати!) играли рок на закрытии первого дня. Годно.

Fastware, Andrei Alexandrescu, The D Language Foundation — эээ, D какбы мертв, а Алексадреску обычно делает доклады на С++ конференциях вроде ACCU, C++Now и прочие, т.к. он легенда С++ мира который ушел в D. Который не взлетел — он хоть и делал там что-то на D в фейсбуке, но в отличии от Rust нехватило момента и язык не смог преодолеть силу тяготелния. Доклад Александреску показался мне немного несобранным и лишенным консистентности — он конечно потрудился чтобы перевести свои примеры в C#, но примеров было мало и они были клинично-прецезионными. Скорость — это не про те языки где нельзя напрямую писать ASM или манипулировать SIMDом, хотя что-то на C# у вас определенно получится. В целом, доклад не особо понравился.

Lightweight microservice collaboration using HTTP, Christian Horsdal — типичный консалтерский доклад высокого уровня. Я еще должен подчеркнуть тут, что microservices are not a thing: коммуникация через веб-интерфейсы это хорошо, но уровень дробления должно быть адекватно, и резать все на “микро” это очень тупо и никому нафиг не нужно. Поэтому просмотрел доклад, не почерпнув ни одной особой мысли. Да, в микросервисах появляются задачи вроде “как описать весь API”, “что нужно делать при поломке”, но эти задачи не отличаются от аналогов с работос с очередями вроде MSMQ, RabbitMQ и всем этим остальным адом. CQRS/ES — это круто и нужно. Микросервисы — скорее все же нет.

Взаимодействие микросервисов по HTTP/2, Евгений Жиров — вот этот доклад мне понравился, т.к. это была реальная case study о том как использовать HTTP/2. Докладчик объяснил зачем им это было надо, на какие грабли они наткнулись и про “а как же .NET Core”. Совершенно годный доклад, и хоть его основной вывод “нужно подождать”, мне понравилось.

Building real world production-ready web APIs with .NET Core, Alex Thissen — еще один доклад по микросервисам который тоже меня не впечатлил. Было чуть больше конкретики, у докладчика был реальный проект, но опять же весь рассказ как-то скомкался воедино, у него не было какой-то общей структуры верхнего уровня, и в результате я не могу сказать чтобы я из него что-то важное. Опять же, microservices aren’t a thing поэтому горевать по ним никто не будет.

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

Вот как-то так. В Москву я на DotNext не поеду — терпеть не могу этот город, и даже когда был евангелистом испытывал от него тошноту. И вообще, в этом году у меня было желание съездить на Build, но всем из РФ закрыли визы, а лететь одному — ну, в принципе реально, но как-то совсем одиноко. Как-нибудь в другой раз, когда санкции снимут. Хотя я почти уверен что их уже вообще не снимут. Ну еще несколько лет точно.

Да, похвастаюсь, у меня вышла книжка. Такие дела.

Реклама

Written by Dmitri

24 апреля 2018 at 22:06

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

Tagged with ,

Итоги 2017 года

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

Обидно когда приходит конец года а у тебя нет желания писать “что мы достигли в 2017”. Все от тебя ждут пост, but you are not helping. Why are you not helping, Leon? (гребаные репликанты)

К слову, новый Blade Runner годный фильм… хотя новых идей там где-то около нуля. Dunkirk тоже годный, хоть этот блог пока и не превратился в обзор кино и прочего (хотя вон по велосипедам в отдельной страничке уже вагон написано).

Этот пост — стена текста без картинок. Не знаю, вроде кому-то такой формат подходит.

Итак, поговорим за достижения. В этом году я худо бедно запилил Kinetica — платформу которая рендерит всякий красивый текст. На базе Кинетики удалось записать один видеокурс, Design Patterns in Java, и несмотря на то что конкретно про механизмы рендеринга которые использовались для курса я не получил слишком много комментов, сам курс людям понравился. Еще бы, я знаю что я делаю. Да, и если кому интересно, Kinetica написана на C# и Direct2D/DirectWrite, и нет, пока что ее скачать нельзя.

В моей голове все больше кристализуется понятие Education 2.0, то есть новый виток обучения. Ествественно, просто красивый анимированный рендер текста — это только начало. Для прогресса в этом плане мне нужна помощь (сейчас у меня 2 сотрудника), время, а также свежие технологии — тут и “мобилка”, и боты, и AR/VR. Будет очень интересно, а главное прибыльно, т.к. с растущей автоматизацией общества (где автономные машины, а?) все большей массе людей придется идти в, гм, автоматизаторы, а это как раз программирование и иже с ним.

Так, что еще? Ну, про алготрейдинг я писать не буду, отчасти потому что “деньги любят тишину”, а отчасти потому что начни я тут писать каким матаппаратом оперирую (пежноство, но у меня реально безумные вещи), у вас просто завянут уши, глаза, или что там. Я могу разве что литературу порекоммендовать, для начала читайте Оксендаля, он годный.

В плане гаджетов… ну, если не брать электровелы (блин, я все еще прусь этой темой), я очень мало нового попробовал. Ну, механические клавиатуры (уже писал вроде на эту тему, советую всем начинать со свич-тестера, первой вашей механической клавой вполне можно брать WASD). Что ещё? iPhone X попробовал, с точки зрения фото он Г, камеры реально некачественные, и я не понимаю, как обзоры в интернете все так подтасовали что ли, короче убогий софт для фото, хотя машинка резвая и вообще. Буду воровать у моей SO для тестирования мобильной платформы.

На 2018 у меня нет никаких технологических амбиций вообще. Вот например ироничная ситуация: у меня комп которому лет 8, у него 3 монитора 1080р… я бы мог сейчас вбросить деньжат и сделать 2×3×4K, там GTX1080, все дела… но парадокс в том, что все итак работает! И приносит деньги! А мотивации затеять новую сборку компа как-то пока нет особо, если учесть насколько все это нудно. Проще просто взять и купить свежий ноут.

Эпоха перенасыщения

У меня в последнее время такое впечатление, что народ насытился и даже пересытился всяким дорогим добром. Помнится, обедая в Аспене, я спросил своих американских коллег, нормально ли что местный веломагазин продает велосипеды по 5 тысяч долларов, по 7… они странно на меня посмотрели и сказали что, ну вообще да, что тут странного? И сейчас я сам уже не понимаю, откуда у нас образовался такой огромный разрыв в доходах между теми кто плывет по течению и тех, кто делает хоть что-то.

Перенасыщение идет во всех сферах. Все кажется пройденным, одним и тем же. Летишь на Канары в 100500-й раз и понимаешь что ничего особенного нет. Смотришь на “распродажи” Massdrop (на самом деле немного разводиловский сайт) и понимаешь что у тебя захламлено абсолютно всё, что тебе не нужен 23-й нож или 5-ю клавиатуру или еще одни наушники. Хотя было бы неплохо.

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

У всех вокруг примерно те же проблемы: short attention span, нет желания ни во что вникать, хочется получить “быстрый контент”. Поэтому меня как раз и не удивляет, что все мои самые прибыльные идеи пришли с максимальной дозой страданий: страдать-то никто не хочет как раз.

Политический сдвиг

Мне, по большому счету, всегда было плевать на политику: я сторонник limited government и мне главное чтобы ко мне не лезли. Санкционная еда у меня имеется (я этот пост в Лапе печатаю, намек!), рутрекер в браузере открывается, чего еще надо? Но если серьезно, та смена парадигмы, которая идет в европе… мне все это очень не нравится, потому что европа — это все еще мой дом.

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

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

В связи с этим понятно почему люди так рады, прям ликуют, победе Трампа. Потому что Трамп большой железнодорожный болт забил на весь этот идиотизм. Он (ну, Мэттис) разбомбил к чертям ИГИЛ (Россия тоже руку приложила!), и класть он хотел на всякие Пакистан, Иран, Палестину и прочий мирок где люди просто завидуют и ненавидят. Трамп делает то что хочет, и ни медиа-цирк ни Голивуд, никто уже не сможет его сместить.

Рано или поздно, окажется что США, с ее жесткой миграционной политикой и реальным движением человечества вперед, останется единственной вменяемой страной… мы будем жить на планете США, подчиненной демократично-капиталичтическим принципам, нравится вам это или нет. А ребятам в платках придется искать себе другую планету, т.к. на этой их никто терпеть не будет. Может на Венеру? Смотрите кто там живет.

Экономические веяния

Все уже знаю что углеводородам хана. Страны Европы постепенно вводят законы чтобы к 2030-40м годам запретить ДВС полностью. Кто-то из вас наверняка захочет поныть в комментах, но ваш лживый национализм неуместен: таким странам как РФ, Саудовская Аравия, Иран/Ирак… им полностью придет хана. Они итак не особо экономически эффективны, но после перехода на электрику, закупать Бентли штабелями уже не получится. Я бы был рад если бы РФ производила, ну, микропроцессоры например, или одежду или мебель на конкурентноспособном уровне, но поезд уже ушел, причем ушел давно. Остается только пилить лес и копать землю.

Вообще автоматизация сильно поменяет расклад… в том смысле что ну не нужно нам 8 миллиардов людей, и кормить их нечем. Я понимаю, всем хочется пожить и понарожать детишек, но извините посоны, планета у нас одна. Достаточно одного качественного скачка (например, бессмертие) чтобы устроить такое расслоение общества, о котором вы и не мечтали. Представьте что у богатых, с их армиями роботов, появится возможность жить без масс рабов. Что они сделают? Правильно, избавятся от всей ненужной биомассы.

Все это, отчасти, мечты и спекуляции, т.к. в ближайшем будущем мы разве то водил попросим освободить авто, т.к. авто будут возить нас сами… посмотрите что делает Uber и прочие в этом плане. Вот это — будущее, с которым сложно спорить. Я все еще хочу понять как получится миссия на Марс если космическая радиация приводит к бесплодию и болезни Альцгеймера.

Что со всем этим делать?

Все делают на что-то ставки. Мне кажется, ставку нужно делать на idle, не-рабочее население у которого есть всё и которое хочет получить интересную информацию, причем прямо в моск. Вот это и нужно строить — игры, шоу на YouTube (идешь в лес, находишь суицидника, записываешь, общее презрение), образовательные программы. Нам нужно занять человека в период перенасыщенности, т.к. есть подозрение что тело, которое не стимулирует внешняя среда, быстрее умирает. А мертвое тело не сможет вам занести денюжку, что как бы критично!

Здесь было напутствие насчет 2018 и мои персональные планы на этот год, но их захавал Сотона. А у меня +100500 к инфантильности, спасибо за прочтение этого опуса. С Новым Годом!

P.S.: критика велкам, как всегда!

Written by Dmitri

4 января 2018 at 0:57

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

Tagged with

Заметка про видеокурсы

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

Самое время поговорить про видеокурсы и то, как я до этого дошел. Если коротко, процесс был такой:

  • Работая в JetBrains, я делал всякие рекламные видео. Их, к сожалению, никто не смотрел. Максимум просмотров было где-то 5к, что в контексте YouTube равно нулю. Но мне за это платили зарплату, так что я делал, и вроде делал неплохо. Скиллы хоть поднял.

  • Я решил сделать свой YouTube канал для того чтобы постить видосики. Также, я постил некоторые из них на TechDays.ru пока ms зами на забили на эту платформу в угоду Channel9.

  • Я понял что мои видосы на YouTube не смотрит ровным счетом никто. 1к просмотров, где-то 2к подписчиков: хвастаться нечем. На yt вообще доход только от рекламы, а я подозреваю что у всех разработчиков стоит AdBlock.

  • В какой-то момент, я вышел на Pluralsight, и начал писать для них курсы. Completion fee (сколько денег платят за завершенный курс) для одного курса где-то $4k. Серьезно, 4к это больше разработческой зарплаты. Так что норм. Особенно если еще и работаешь.

  • Пассивный доход с ps был где-то в районе нуля. А потом один курс выстрелил. Причем самый нежданный. И понеслась.

  • Я сделал много видосиков для ps, а также рискнул сделать что-то на Udemy. К сожалению на Udemy у меня доход был где-то нуль, поэтому я на это подзабил.

  • В какой-то момент я решил выкладывать на Udemy все, что я изучаю в данный момент. Просто «в потоке». И постепенно оно начало более менее приносить.

  • Далее во времени, я насобачился маркетить в Udemy и вот тут уже пошел заметный love.

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

Впрочем, я не против. Я когда делаю курсы, я делаю их хорошо. Очень хорошо. Качество везде. Иначе не выдержать конкуренции, да и вообще, самооценка, «ачивки» — это не только деньги (они уже не волнуют), это когда ты победил себя и поставил все точки над i. ■

Written by Dmitri

27 августа 2017 at 22:54

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

Использование ImpromptuInterface в реализациях паттернов проектирования

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

Вы наверняка уже знаете, что если отнаследоваться от DynamicObject, можно получить разные плюшки от DLR, такие как например возможность вызывать методы класса даже если у класса их нет, и все такое. Но знаете ли вы что можно «вывесить» этот ваш DynamicObject как абсолютно любой интерфейс? Зачем это надо, спросите вы — ну, для того чтобы реализовывать всякие паттерны проектирования. Вот парочка примеров.

Как это работает?

После добавления NuGet пакета ImpromptuInterface, у вас появится extension-метод ActLike<T>(), который будет доступен на всех объектах типа object. Параметр T в данном случае – это именно тип интерфейса который хочется вывесить наружу.

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

А теперь давайте посмотрим для чего это надо.

Паттерн Null Object

Самый простой пример — это паттерн Null Object, т.е. объект, который ровным счетом ничего не делает.

Вот например, у вас есть интерфейс логирования

public interface ILog
{
  void Info(string msg);
  void Warn(string msg);
}

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

public class BankAccount
{
  private ILog log;
  private int balance;
  public BankAccount(ILog log)
  {
    this.log = log;
  }
  public void Deposit(int amount)
  {
    balance += amount;
    // check for null everywhere
    log?.Info($"Deposited ${amount}, balance is now {balance}");
  }
  public void Withdraw(int amount)
  {
    if (balance >= amount)
    {
      balance -= amount;
      log?.Info($"Withdrew ${amount}, we have ${balance} left");
    }
    else
    {
      log?.Warn($"Could not withdraw ${amount} because balance is only ${balance}");
    }
  }
}

Представьте теперь, что логирование вам нафиг не сдалось. Что делать? Передавать null нельзя, будут NRE везде где вызовы, ведь safe call operator ?. повсеместно разработчики, как правило, не используют. Ждем non-nullable types в C#8 или позже.

Один вариант — просто сделать фейковый класс:

public sealed class NullLog : ILog
{
  public void Info(string msg)
  {
    
  }
  public void Warn(string msg)
  {
    
  }
}

Это и есть «каноническая» реализация паттерна Null Object, но она получается легко только когда членов у класса мало, или же ReSharper под рукой. В целом, не хочется писать всю эту муть, особенно если вас не так сильно волнует перформанс.

Итак, третий, «ядерный» вариант с использование ImpromptuInterface. Делаем класс со следующей сигнатурой:

public class Null<T> : DynamicObject where T:class
{
  ⋮

T параметр выше – как раз тип интерфейса. Теперь можно сделать из класса этакий псевдо-singleton (на самом деле нет, создаем каждый раз):

⋮
public static T Instance 
{
  get
  {
    if (!typeof(T).IsInterface)
      throw new ArgumentException("I must be an interface type");
    return new Null<T>().ActLike<T>();
  }
}
⋮

Магия в том, что мы делаем Null<T>, сохраняя тип аргумента, но вывешиваем его не как Null<T> а просто как T.

А что дальше? Дальше нам нужно перехватывать вызовы методов, например:

  ⋮
  public override bool TryInvokeMember(InvokeMemberBinder binder, 
    object[] args, out object result)
  {
    result = Activator.CreateInstance(binder.ReturnType);
    return true;
  }
}

Код выше смотрит на тип возвращаемого значения метода и берет его дефолтную реализацию, т.е. вызывает new T() через System.Activator — это конечно же чревато тем, что если у класса нет дефолтного конструктора, то result будет равен null что очень грустно но не критично если вы не будете пытаться этим результатом пользоваться.

Вот собственно и всё, наш null object готов:

var log = Null<ILog>.Instance;
var ba = new BankAccount(log);
ba.Deposit(100); // логирования не будет

Динамическая Прокси

А теперь давайте представим «обратный» сценарий: у вас есть класс банковского счета, вы хотите добавить логирование «со стороны».

public interface IBankAccount
  {
    void Deposit(int amount);
    bool Withdraw(int amount);
    string ToString();
  }
  public class BankAccount : IBankAccount
  {
    private int balance;
    private int overdraftLimit = -500;
    public void Deposit(int amount)
    {
      balance += amount;
      WriteLine($"Deposited ${amount}, balance is now {balance}");
    }
    public bool Withdraw(int amount)
    {
      if (balance - amount >= overdraftLimit)
      {
        balance -= amount;
        WriteLine($"Withdrew ${amount}, balance is now {balance}");
        return true;
      }
      return false;
    }
    public override string ToString()
    {
      return $"{nameof(balance)}: {balance}";
    }
  }

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

public class Log<T> : DynamicObject where T : class, new()
{
  private readonly T subject;
  private Dictionary<string, int> methodCallCount =
    new Dictionary<string, int>();
  protected Log(T subject)
  {
    this.subject = subject;
  }
  ⋮

Далее можно сварганить фабричный метод, который будет делать тип Log<T> и выдывать его как интерфейс I. Например:

⋮
public static I As<I>() where I : class
{
  if (!typeof(I).IsInterface)
    throw new ArgumentException("I must be an interface type");
  return new Log<T>(new T()).ActLike<I>();
}
⋮

Теперь нам нужно сделать так, чтобы при вызове метода, метод-то вызывался, но кол-во методов тоже записывалось. Для этого нужно перегрузить TryInvokeMember():

⋮
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
  try
  {
    // logging
    WriteLine($"Invoking {subject.GetType().Name}.{binder.Name} with arguments [{string.Join(",", args)}]");
    // more logging
    if (methodCallCount.ContainsKey(binder.Name)) methodCallCount[binder.Name]++;
    else methodCallCount.Add(binder.Name, 1);
    result = subject.GetType().GetMethod(binder.Name).Invoke(subject, args);
    return true;
  }
  catch
  {
    result = null;
    return false;
  }
}
⋮

Что делает код выше? Ну, помимо логирования количества вызовов, он через reflection вызывает нужный метод на объекте и сохраняет результат. Если вдруг падает исключение — что поделать, бывает, пишет в результат null, и всё.

Ну и наконец, если вам вдруг надо чтобы работал честный ToString() на объекте, но он декорировал существующий ToString() того типа, на котором ведется логирование, то это можно сделать вот так:

  ⋮
  public string Info
  {
    get
    {
      var sb = new StringBuilder();
      foreach (var kv in methodCallCount)
        sb.AppendLine($"{kv.Key} called {kv.Value} time(s)");
      return sb.ToString();
    }
  }
  
  // will not be proxied automatically
  public override string ToString()
  {
    return $"{Info}{subject}";
  }
}

Вот собственно и всё, теперь можно юзать вот эту проксю как-то вот так:

var ba = Log<BankAccount>.As<IBankAccount>();
ba.Deposit(100);
ba.Withdraw(50);
WriteLine(ba);

Заключение

Возможность вывесить любой интерфейс из DynamicObject-а бывает очень удобной. Идинственное что за эту возможность придется платить перформансом, так что класть это в продакшн или нет – решать вам. Но в тестировании бывает полезно.

Если вам интересны паттерны, у меня есть более 10 часов видосиков по паттернам в C#/.NET. Enjoy!

Written by Dmitri

4 мая 2017 at 12:41

Опубликовано в C#, Design Patterns

.NET-ный enum в стиле Rust

2 комментария

Как вы уже наверное знаете, у разных языков (C#, Java, Rust, …) понимание того что такое enum (перечисление) абсолютно разное. Иногда мне в C# хочется чтобы было как в Rust, да и в C++ тоже. Поэтому это рассказ про то, как сделать «хорошо» на примере C#.

Однородный тип

Давайте начнем. Например, чтобы писать

Color color = Color.Red;

нужно чтобы Color.Red имел тот же тип что и Color. То есть, если утрировать, можно и так написать:

public class Color
{
  private byte r, g, b, a;
  public Color(byte r, byte g, byte b, byte a)
  {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }
  public static Color Red = new Color(255, 0, 0, 255);
}

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

Что-то мы потеряли, а что-то приобрели. Потеряли мы все Enum.GetValues(), т.е. возможность получить все предопределенные цвета. Как их вернуть? Ну наверное как-то вот так:

public static Color Red = new Color(255, 0, 0, 255);
public static Color Blue = new Color(0, 0, 255, 255);
public static Color[] Values = new[] {Red, Blue};

Что ещё продолбано? Ну, имена этих переменных. Ой! А вот это непросто будет сделать:

private BiDictionary<string, Color> colors = new BiDictionary<string, Color>
{
  ["Red"] = Red,
  ["Blue"] = Blue
};
public IEnumerable<string> Names => colors.Keys;
public IEnumerable<Color> Values => colors.Values;
public Color this[string name] => colors[name];
public string this[Color color] => colors.Reverse[color]; // возможно излишне?
private string ToStringImpl()
{
  return $"{nameof(r)}: {r}, {nameof(g)}: {g}, {nameof(b)}: {b}, {nameof(a)}: {a}";
}
public override string ToString()
{
  if (colors.Reverse.ContainsKey(this)) return colors.Reverse[this];
  return ToStringImpl();
}

BiDictionary как вы поняли – это словарь который работает в обе стороны, чтобы можно было быстро искать. Реализации подобного есть в тырнете, плюс самому на коленке написать тоже несложно.

Итак, всё ли мы реализовали? Да вроде да, можно использовать

Color red = Color.Red;
Color my = new Color(255, 0, 255, 0);

и вывод будет вот такой:

Red
r: 255, g: 0, b: 255, a: 0

Дискриминированное объединение

Пока все просто, давайте пример посложнее уже. Типичный пример вычисления со всякой неоднородной начинкой выглядит как-то так:

type IntOrBool =
  | Boolean of bool
  | Integer of int // это не раст, это F#

Ха! То есть смотрите, у вас есть два абсолютно разных типа, с разными данными, и они должны приводиться к некому типу IntOrBool. Первое что приходит в голову — это абстрактный класс вроде

public abstract class IntOrBool
{
  public int Tag => this is Integer ? 0 : 1;
  public bool IsInteger => this is Integer;
  public bool IsBoolean => this is Boolean;
  public static Boolean NewBoolean(bool value) => new Boolean(value);
  public static Integer NewInteger(int value) => new Integer(value);
}

Ну и соответственно каждый из наследников должен выглядеть как-то вот так:

public class Integer : IntOrBool
{
  public int Value { get; set; }
  public Integer(int value)
  {
    Value = value;
  }
  // делаем вид что мы int
  public static implicit operator Integer(int value)
  {
    return new Integer(value);
  }
  public static implicit operator int(Integer integer)
  {
    return integer.Value;
  }
}

Но дальше у нас те же проблемы что и ранее — как сделать ToString(), как вывести список всех возможных типов объединения? Мы точно знаем как это делает F# — он для этого использует атрибуты, и по сути поиск всех классов превращается в использование Reflection. Что уныло. А что если сделать вот так?

public static Type[] Cases = new[] {typeof(Boolean), typeof(Integer)};

В конечном счете, что мы теряем? Ладно, пора уже попробовать использовать все это в типичном (насколько это возможно для такого синтетического примера) сценарии:

IntOrBool foo = 0; // FAIL :(

Опа, уже нифига не работает. Хотели удобство а получилось не очень. Что, еще операторов? Пожалуйста

public static implicit operator IntOrBool(int value) => NewInteger(value);
public static implicit operator IntOrBool(bool value) => NewBoolean(value);

ок, теперь можно инициализировать, а можно ли проверять if-ом?

IntOrBool foo = 0;
if (foo is Integer bar)
{
  int i = bar;
  WriteLine(i);
}

Кривоватенько, но работает. foo is int bar конечно же писать нельзя. Ну и выборку по шаблону в стиле case Integer(i) пока тоже в C# не сделали.

Да, насчет ToString() — тут будет просто GetType().ToString() для кейсов у которых нет собственных данных. То есть будут порождаться типы даже там, где они не нужны. Для F# это как бы норма (в F# например, любой переданный оператор превращается в struct), но в C# это не очень хорошо, хотя опять же, ничего критичного.

Вообщем вот такой финт ушами, не дающий особых плющек, т.к. ко всему этому нужен умный switch и полноценный pattern matching. Да, и Deconstruct(), если вы его реализуете, не поможет для типов которые имеют один member. Сорри! ■

Written by Dmitri

14 апреля 2017 at 0:47

Опубликовано в C#

Tagged with

Работа лето интерн C++ программист студент графика :)

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

Вообщем, тут такое дело. У меня есть система кинетической типографики в достаточно зачаточном состоянии, и я подумал что может кто-нибудь с начинающими скилами С++ захочет покодить ее за деньги в летние месяцы этого года. И денежку получить и опыт какой-никакой.

Система моя называется Kinetica, она написана на C++ с использованием Direct2D/DirectWrite и FFMPEG. По сути, это программа, которая просто генерит анимированный текст (в видеофайлах), и мне нужен кто-то, кто

  • Поанализирует мои запросы на разные механизмы анимации

  • Напишет код который эту анимацию сделает (хочется красивое API)

  • Напишет какие-нибудь тесты для всего этого

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

Касательно навыков, от вас требуется

  • Знание С++ и как на нем делать приличное ООП

  • Навыки работы с Visual Studio

  • Некоторое понимание компьютерной графики

  • Умение работать с Git (я использую GitLab)

  • Умение работать с баг трекером

Вообщем если вы студент, учили С++ и думаете «а зачем мне оно надо», то тут есть шанс погенерить интересные видео, поднять скилы, и все такое. Работа фултайм, задач будет много — как на создание красивого API, так и на создания на этом API нужных мне анимаций. Которых будет много.

Деньги — самое главное. $1k/месяц пейпалом. Работа, как вы поняли, удаленная.

Вопросы, пожелания, пишите, отвечу что да как.

Written by Dmitri

16 марта 2017 at 1:10

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

Что нового в C# 7

2 комментария

Ну что, всех можно поздравить с релизом VS2017? Для начала, я пожалуй признаюсь что я давно уже (время наверное на месяцы пошло) использую Rider (и даже запилил по нему видеокурс), но релиз VS2017 — это не только релиз слегка медленной, 32-битной Windows-only IDE которая даже не умеет правильно рисовать лигатуры. Это еще и релиз новых версий компиляторов — именно того, что Microsoft нам обещали делать out-of-band, но обещание оказалось наглой ложью, как для C++ так и для C#.

Что же, давайте посмотрим что там в новом сишарпике…

Локальные методы

Вообще, проблемы с локальными методами толком не было, т.к. в любой функции можно декларировать лямбда-переменную, что и есть по сути метод. Так что в C#7 это разве что привели в более удобоваримый вид:

public static Tuple<double,double> Solve(double a, double b, double c)
{
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  var disc = CalculateDiscriminant();
  return Tuple.Create(
    (-b + Math.Sqrt(disc)) / (2 * a),
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

Что мы можем заметить в примере выше? Правильно, вложенная функция умеет захватывать окружение, в т.ч. аргументы внешней функции. И конечно же дать функции CalculateDiscriminant аргументы a,b,c не получилось бы, т.к. эти имена уже заняты.

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

Касательно полезности фичи: мне норм. Этот подход позволяет плодить меньше членов на уровне класса, а баловаться с лямбдами напряжно т.к. для любой лямбды нужно определить тип Func<int,Foo,string> и это порой бывает утомительно — хотя в некоторых случаях Решарпер помогает. Вообще было бы лучше если бы нам позволяли еще делать локальные статические переменные прямо в методах, как это позволяет делать C++.

Out-переменные

Обычно как было: декларация переменной это statement. Все, теперь это по сути expression т.к.:

string s;
if (DateTime.TryParse(s, out DateTime dt))
{
  // используем dt
}

Это чисто «сахарная» фича, и резонным будет вопрос — а что если DateTime не пропарсится а мы все равно попытаемся ей воспользоваться? Ну, тут все просто — у этой штуки будет дефолтное значение. Но зато заметьте, dt имеет тот же scope, что и строка s.

Кортежи

Кортежи уже были в C#, но вставлены они были криво: все эти Item1, Item2 не добавляли к удобству пользования, и кому-то могло показаться что рано или поздно нам вообще подсунут variadic templates. Но разрабы поймали себя и все-таки сделали что-то годное:

public static (double x1, double x2) Solve(double a, double b, double c)
{
  var disc = CalculateDiscriminant();
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  return (
    (-b + Math.Sqrt(disc)) / (2 * a), 
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

И вызвать можно это вот так:

var (x1, x2) = QuadraticEquationSolver.Solve(1, 10, 16);
Console.WriteLine(x1 + ", " + x2);

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

var names = (first: "Dmitri", last: "Nesteruk");
Console.WriteLine(names.first);

Как видите, кортеж определяется как тип через скобочки и список типов (т.е. (double,double) условно-эквивалентно ValueTuple<double,double>). Но ложка дегтя тут огромная: для всего этого нужен внешний пакет System.ValueTuple. Что?!?!?

Спокойно, очевидно что просто эту тему недопилили и ValueTuple<T, ...>, который должен был быть в .NET 4.6.2 BCL просто туда не попал. Что это за класс? Да просто еще одна реализация кортежа, но в этот раз вся ее суть закрыта синтаксическим сахаром вроде того что я привел выше. Если кому интересно что под капотом, вот сорцы.

Expression-bodied members

На момент C#6, некоторые вещи можно было писать как expression bodies, т.е например void Foo(x) => x+1; делал вполне себе валидный метод. Сейчас же, это счастье распространилось помимо методов и пропертей еще на конструкторы/деструкторы, геттеры и сеттеры:

class Person
{
  Person() => Names = new[]{"Nameless One"};
  public int FirstName
  {
    get => Names[0]; // ну вы поняли
  }
}

Вообще вся эта expression bodied тематика экономит хорошо так символов. Единственное что мне не нравится это каша вроде

class Person
{
  bool CanVote => Age <= 16; // без лигатур - коряво :(
}

но это просто один конкретный пример того как можно получить нечитаемый код.

Throw Expressions

Тут мне 2017 студия осмелилась подправить Решарперный код. Было что-то вроде этого:

class Foo
{
  Foo(Bar bar)
  {
    if (bar == null) throw new SomeException();
    this.bar = bar;
  }
}

но теперь оказывается можно писать вот так

class Foo
{
  Foo(Bar bar)
  {
    this.bar = bar ?? throw new SomeException();
  }
}

Интересный ход конем, однако. Хорошо ли бросать из места где ожидается конкретный return value? Ну, если компилятор может это безопасно обработать, почему бы и нет — главное потом не пытаться прочитать поле, которое мы так и недописали.

Pattern matching

Когда-то я писал про самопальный pattern matching, но тут сделали реальную реализацию.

Начнем с простого — теперь проверку на тип можно сделать прямо в if-е и сразу получить и проверку и приведенный тип:

if (shape is Rectangle rc)
{
  var area = rc.Width * rc.Height;
}

Ну и потом нам разрешили делать switch на этом месте:

Shape s = new Rectangle { width = 0, height = 0 };
switch (s)
{
  case Rectangle rc when (rc.width == rc.height):
    Console.WriteLine("It's a square!");
    break;
  case Circle c:
    break;
}

Заметьте ключевое слово when выше. Ничего не напоминает? Правильно, F#. Но конечно F# все еще лидирует в этом плане. Хотя бы потому, что в F# можно делать алгебраические типы данных, что очень полезно когда ты, например, что-нибудь структурированное парсишь, например MathML.

Ну хорошо, смотрите, хоть что-то сделали. Конечно, нельзя мэтчить на Rectangle(42, double h) — это откинули на будущее. И switch на кортежах тоже пока не работает. Грусть-печаль.

Ref returns

Как вернуть элемент массива by reference? Ну строго говоря, можно запинить весь массив и попытаться вернуть указатель в unsafe. Но теперь есть более гуманный способ — ref на чем угодно.

int[] numbers = new int[] { 1, 2, 3 };
ref int refToSecond = ref numbers[1];
refToSecond = 123;
Console.WriteLine(string.Join(",",numbers)); // 1, 123, 3

Хмм, ну это хорошо конечно, но со списком такое не пройдет

List<int> numbers = new List<int> { 1, 2, 3 };
ref int second = ref numbers[1]; // A property or indexer cannot be used⋮

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

Все это — простой aliasing, то есть еще одно имя для той же самой переменной. Это не «ссылка на область памяти» т.к. это обычно требует слово fixed чтобы вменяемо работать в unsafe контексте, а у нас все тут safe.

Да, теперь можно и из метода ref на переданный ref возвращать, то есть возможно вот такое мракобесие:

static ref int Min(ref int x, ref int y) {
  if (x < y)
    return ref x;
  else
    return ref y;
}
⋮
int a = 1, b = 2;
 ref int c = ref Min(ref a, ref b);
c = 123; // a == 123!!!

Holy s~t, посмотрите сколько тут раз ref написано. Даже в вызове используется ref Min(...) потому что если ты опустишь этот ref это значит что тебе не нужен референс на значение, а нужно само значение.

int d = Min(ref a, ref b); // вполне валидно, d == 1

Проблема тут в том, что этот ref — это то же самое что референсы в C++, из-за которых теперь в уютном сишарпике можно писать треш вроде Min(ref a, ref b) += 123 (по сути делает a += 123), что абсолютно отвратительно для читания и понимания. К тому же, неопытные программисты тут же будут ломиться и возвращать адреса локальных переменных, что делать нельзя:

static ref int Foo()
{
  int x = 0;
  return ref x; // не скомпилируется - cannot return local by reference
}

Хотя помнится что году так в 2012, Липперт угрожал что они могут и этот сценарий поддержать.

Что еще тут?

Теперь можно делать подчеркивания в литералах: int x = 123_456;. Также появились бинарные литералы — теперь можно писать var literal = 1100_1011_____0101_1010_1001;. Подчеркиваний может быть сколько угодно.

Pattern matching — все самое вкусное не попало в C#7, ждем следующих версий. Должны быть всякие вот такие штуки поддержаны:

switch (foo)
{
  case 123: ⋮
  case MyEnum.Stuff: ⋮
  case string s: ⋮
  case Point(int x, 321) where x > 0: ⋮
  case Point(12, 23) : ⋮
  default: ⋮
}

Record types — не вошли, будут в C#8 наверное.

Ну вообщем как-то так. А вы заметили насколько шустрее стал компилятор C#7? Серьезно, видимо опять что-то подкрутили с инкрементальностью. Кстати, в VS2015 сделали обалденные улучшения инкрементальности C++, а тут кажется улучшили C#. У меня «горячая» компиляция летает, хотя «холодная» все еще тормозит как и раньше.

Наверняка что-то еще из фич забыл, напишите в комментариях. ■

Written by Dmitri

13 марта 2017 at 10:54

Опубликовано в C#