Re[9]: Опциональные типы
От: meadow_meal  
Дата: 26.02.17 09:14
Оценка:
Здравствуйте, vdimas, Вы писали:

V>А почему бы не сделать обновление автоматизированным (типа как делают ORM), где прикладной код писать в колбэках навроде onClanChanged?

V>Тогда новым коллегам вообще не придется возиться с транспортным слоем или возиться по-минимуму.

Да, это возможно. Я упоминал о расширяемом кодогенераторе, и чтобы не быть совсем голословным, приведу пример:
  Скрытый текст
public class DeltaGenerator : ICsGenerator
{
    string FieldUpdate(RecordForm rec, RecordField field) => 
        $@"if (update.{field.csName}.HasValue) this.{field.csName} = update.{field.csName}.Value;";

    public void Generate(CsModel model, Module mod)
    {
        foreach (var rec in mod.Records)
        {
            var deltaClass = rec.StringAttribute("delta.class", null);
            if (deltaClass != null)
            { 
                var deltaFile = rec.StringAttribute("delta.file", mod.csFileName);
                var @class = model.File(deltaFile).NamespaceClass(deltaClass);
                @class.Partial = true;
                @class.Method($@"
public void Update({rec.csTypeName} update)
{{
{rec.Fields.Join(f => FieldUpdate(rec, f))}
}}
");
            }
        }
    }
}

Этот скрипт расширения (который я только что набросал за пару минут) генерирует C# код, подобный тому, что я приводил, и который вызвал критику. Объявить здесь же свойства, события, что угодно, или вызвать коллбэки — очевидно, не проблема. Используется следующий образом:
[csharp delta.class="Game.Unit" delta.file="Unit.Generated.cs"]
record UnitUpdate
{
   ...
}

Partial класс Unit будет сгенерирован в нэймспэйсе Game в файле Unit.Generated.cs.

Однако еще раз скажу, почему этого нет. Чтобы решить задачу в общем случае, нужно в первую очередь стандартизировать способы обновления коллекций. (А их много разных, так как коллекции бывают упорядоченными и неупорядоченными списками, упорядоченными и неупорядоченными словарями, в какие-то элементы только добавляются, из каких-то и удаляются, какие-то всегда обновляются целиком, для каких-то обновление элемента это в свою очередь дельта). Эта работа еще не проделана (а возможно никогда и не будет), и приоритет у нее низкий. Потому что таких функций обновлений в одном проекте всего несколько мест строк на двести-триста в сумме, а писать код реакции (как правило, тривиальный) под условием HasValue, пусть и не так изящно как в коллбэках, но не менее удобно.

V>Еще идея.

V>Если уж у вас собственный DSL и собственный маппинг (кодогенерация) на типы, то можно ввести такую штуку
V>(условный синтаксис):
V>
V>type ClanId = Optional<int> where NotSet = 0;
V>type Coord = Optional<uint> where NotSet = ~0; // 0xFFFFFFFF
V>


Сейчас можно так:
define ClanId ?int;

(?int это шорт алиас для Optional<int>; странноватый и не факт что удачный ? перед типом а не после, потому что изначально это был модификатор поля рекорда, а не тип, а потом с развитием системы типов оставили как есть).

Что касается аспектов DSL (про генерируемый C#-код — ниже):

ИМХО, ClanId — должен быть int, а не Optional<int>. Скажем сервисная функция GetClanInfo(ClanId clan_id) не должна принимать NotSet, а GetPlayerInfo может вернуть NotSet в поле ClanId, и я бы предпочел, чтобы в обоих случаях это было отражено в сигнатуре.

Одна из возможных мотиваций для констант после where — экономия трафика — тоже не проходит, так как если не использовать variable encoding, то для NonSet получим 4 байта вместо 1 байта (а часто — бита), что в среднем даст худший результат, а с variable encoding мы стараемся быть осторожнее, так как в случае частого использования и большого числа мелких пакетов нагрузка на cpu может дать больше вреда, чем минимальная экономия трафика — пользы (впрочем, если до этого дойдет, нужно конечно замерять, задумываться об этом имеет смысл лишь для очень частых событий).

V>Для C# генерить примерно такой маппинг:

  Скрытый текст
V>
V>struct ClanId {
V>    public int Value;
V>    public bool HasValue { get { return Value != 0; }}
V>}

V>struct Coord {
V>    public int Value;
V>    public bool HasValue { get { return Value != ~0; }}
V>}

V>void Update(ClanId clanId) {
V>    if(clanId.HasValue) ...
V>}
V>

V>Тогда размер структуры в памяти будет минимален и уже имеющийся код можно будет оставить без изменений.


Здесь мы тоже не можем по типу отличить обязательно наличествующее значение от опционального. Небольшая экономия памяти не кажется достаточной компенсацией.

Ну и не работает обобщенный код для Nullable и ??, что может быть или не быть неудобством в конкретных случаях.

Но честно говоря, я не понимаю, зачем нужно искать замену Optional в случае его использования по самому что ни на есть прямому назначению.

Однако, в любом случае спасибо за предложения. (Без обид, но думать о том, чтобы все бросить и переписать на ASN.1, ANTLR, BLToolkit и т.п. мне давно уже поздно, а о том, как люди воспринимают текущее решение, какие недостатки видят и какие альтернативы предпочитают — это полезно, и хоть я и отстаиваю свою точку зрения, но все идеи и предметную критику записываю. Нужно ли что-то менять в системе апдейт-рекордов — вопрос для нас еще открытый).
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.