Что значит опциональные поля

oess_1.2.0. Опциональные поля

Введение

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

Опциональным полем является атрибут объекта, которому можно назначить значение по-умолчанию, использующееся в подавляющем большинстве случаев. Если при сериализации проверить, что поле содержит такое значение, то поле можно не сериализовать. Достаточно указать в двоичном представлении, что поле имеет значение по-умолчанию. При десериализации можно определить, что поле не было сериализовано и назначить ему значение по-умолчанию сразу после десериализации всего объекта.

Пример. Пусть есть некое сообщение handshake, в котором требуется передать параметры коммуникационной сессии. Среди прочих параметров в нем содержатся: параметры использования подписи передаваемых данных и параметры компрессии передаваемых данных. При этом, в большинстве случаев в качестве подписи будет использоваться алгоритм md5. Компрессия по умолчанию не используется, но если используется, то обычно применяется gzip со средней степенью сжатия. Представить это на DDL можно следующим образом:

Теги DDL для описания опциональных полей

Дополнения во вспомогательном коде

В версии 1.2.0 для приведенного выше DDL описания методы oess_present_if, oess_set_default будут иметь вид:

Источник

Опция, как маркетинговый ход

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

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

Что значит опциональные поля. Смотреть фото Что значит опциональные поля. Смотреть картинку Что значит опциональные поля. Картинка про Что значит опциональные поля. Фото Что значит опциональные поля

О чём речь?

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

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

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

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

То же самое и с любым другим бытовым прибором, компьютером или ноутбуком. К ноутбуку с помощью порта USB опционально можно подключить телефон, фото- и видеокамеру, чтобы скачать на жёсткий диск фотографии или видео, снятые на отдыхе и в гостях. Можно присоединить переносной носитель информации или web-камеру.

Что значит опциональные поля. Смотреть фото Что значит опциональные поля. Смотреть картинку Что значит опциональные поля. Картинка про Что значит опциональные поля. Фото Что значит опциональные поля

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

Удобно это или нет?

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

Что значит опциональные поля. Смотреть фото Что значит опциональные поля. Смотреть картинку Что значит опциональные поля. Картинка про Что значит опциональные поля. Фото Что значит опциональные поля

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

Источник

Использование std::optional в С++17

Что значит опциональные поля. Смотреть фото Что значит опциональные поля. Смотреть картинку Что значит опциональные поля. Картинка про Что значит опциональные поля. Фото Что значит опциональные поля

Давайте возьмём пару от двух типов — что вы можете сделать с композицией подобного рода?

В этой статье я расскажу вам про std::optional — новый вспомогательный тип, добавленный в C++17. Это обёртка для вашего типа и флаг показывает, инициализировано ваше значение или нет. Давайте посмотрим, где это может быть полезно.

Вступление

Добавлением логических флагов к другим типам вы можете достичь то, что называется «Nullable типы». Как было сказано ранее, флаг используется для обозначения того, доступно значение или нет. Такая обёртка выразительно представляет объект, который может быть пустым (не через комментарии :).

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

Этот тип является типом-значением (value-type) (таким образом, вы можете копировать его). Более того, для std::optional не нужно отдельно выделять память.

Использование

Обычно, опциональный тип может быть использован в следующих сценариях:

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

Простой пример

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

Серия

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

Ресурсы по C++17 STL:

Создание std::optional

Есть несколько вариантов создания std::optional :

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

Например, вы можете написать:

Я расскажу про std::in_place позже, не переключайте канал и оставайтесь с нами.

Возврат std::optional из функции

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

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

Получение значения

Возможно, самая важная операция для опционального типа (помимо его создания) — это то, как вы можете получить сохранённое значение.

Для этого есть несколько вариантов:

Таким образом, наиболее удобно, возможно, будет проверить, есть ли реальное значение в опциональном объекте, и затем использовать его:

Возможности std::optional

Давайте посмотрим, какие ещё есть возможности у опционального типа:

Изменение значения

Вот небольшой пример:

Этот код доступен здесь: @Coliru.

Сравнения

При выполнении кода выше, будет выведено:

Этот код доступен здесь: @Coliru.

Примеры с std::optional

Ниже вы найдёте два примера, где std::optional подходит идеально.

Имя пользователя с необязательным никнеймом и возрастом

Этот код доступен здесь: @Coliru.

Парсинг целых чисел из командной строки

Этот код доступен здесь: @Coliru.

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

Другие примеры

Производительность и анализ использования памяти

Если подходить абстрактно, то ваша версия STL может реализовывать опциональный тип данных как:

В кратце, std::optional просто оборачивает ваш тип, подготавливает место для него и добавляет один логический параметр. Это означает, что он увеличит размер вашего типа в соответствии с правилами выравнивания.

Есть один коментарий для этой конструкции: «Ни одна стандартная библиотека не сможет реализовать std::optional так (она должна использовать union из-за constexpr )». Поэтому код выше просто демонстрирует пример, а не реальную реализацию.

Правила выравнивания важны, как говорит стандарт:

Например, если у вас есть такой тип:

То он займёт больше места, чем если бы вы использовали свой тип вместо std::optional :

В первом случае размер структуры равен 32 байтам! Во втором случае всего лишь 24.

По ссылке великолепное объяснение насчёт производительности и использованию памяти, взятое из документации boost: вопросы производительности.

И в статье «Эффективные опциональные значения» автор рассуждает, как написать обёртку для опционального типа, которая может быть немного быстрее.

Интересно, есть ли шанс использовать хоть какую-то магию компилятора и повторно использовать некоторое пространство, чтобы поместить этот дополнительный флаг «инициализации объекта” внутри опционального типа. Тогда бы никакого дополнительного пространства не было бы необходимо.

Особенный случай: std::optional и std::optional

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

std::optional ob — о чём это говорит? С этой конструкцией вы имеете логический тип с тремя состояниями. Поэтому, если он вам и правда нужен, возможно лучше использовать настоящий троичный тип — std::tribool boost::tribool (правка: Antervis).

Более того, использование такого типа может сбивать с толку, потому что ob преобразуется в bool если в нём внутри есть значение и *ob возвращает хранимое значение (если оно доступно).

Похожая ситуация может проявиться с указателями:

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

Фух! Да, это было очень много текста про опциональный тип, но это не всё.

Тем не менее, мы рассмотрели основное использование, создание и оперирование эти удобным типом. Я считаю, что у нас есть много случаев, когда опциональный тип подходит намного лучше, чем использование некоторых предопределённых значений для представления nullable типов.

Я бы хотел напомнить следующие вещи про опциональный тип:

Источник

Telegram на go, часть 2: бинарный протокол

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

Клиент на Go продолжает развиваться, а мы вернемся в прошлое и вспомним, как писался для него сериализатор и десериализатор протокола.

Основы

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

Получится вот такая структура:

И еще, MTProto в основном выравнивает значения по 4 байтам (32 битам), вынесем это в константу:

Сериализация

Зная, что почти всё в MTProto little-endian, мы можем начать с сериализации uint32:

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

Основная сложность заключается в том, чтобы аккуратно обработать все типы, так как подробных примеров в документации нет. Приходится искать примеры по тестам у других клиентов, например grammers, замечательного клиента на Rust для Telegram.

Десериализация

Возможно, не стоило использовать одну и ту же структуру для десериализации и сериализации, но на практике это оказалось удобным, а сам пакет gotd/td/bin получился довольно компактным.

Продолжим пример с uint32:

Строки

Строки ( []byte и string ) сериализуются по-разному в зависимости от их длины и выравниваются по 4 байтам.

Если длина меньше или равна 253, то она пишется в первый байт, а затем строка:

Если длина больше, то первым байтом будет 254, потом три байта длины в little-endian, а затем уже строка:

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

Структуры

Мы базово разобрали, как происходит работа со значениями, но нам нужно сериализировать сложные типы. Делается это довольно просто: сначала пишется ID типа (это те самые #5b38c6c1 из схемы, просто как uint32), а затем все его поля по порядку, как в схеме.

Например, у нас есть такая структура (это упрощенный выдуманный пример из теста для сериализатора):

И сериализовать её c помощью нашего Buffer довольно просто:

Если попробовать вызвать Encode, в буфере будет следующее содержимое:

Для десериализации мы должны сначала проверить, что у нас действительно нужный тип. Лучше это делать без изменения Buf, а значит нам потребуется новый метод:

Я опущу тело метода ConsumeID(id uint32) для краткости: он вызывает PeekID и возвращет ошибку на неожиданный тип, сдвигая буфер только в случае успеха. С помощью него получится вот такая десериализация:

Теперь мы можем аналогично написать (де-)сериализатор для любого типа из схемы, имплементируя следующие интерфейсы:

Специальные случаи

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

Массивы

Называются векторами. Например:

Тут понятно, как сериализовать title, но что делать с users?

Непосредственно Vector определен вот так:

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

На практике вектор сериализуется следующим образом: сначала пишется тип вектора (0x1cb5c415), затем количество элементов, а потом уже сами элементы подряд:

Булевые значения

С ними примерно так же, как и с векторами, но чуть проще:

Таким образом, тобы записать Bool, нужно написать либо 0x997275b5, либо 0xbc799737:

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

Флаги и опциональные поля

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

Особым случаем опционального поля являются булевые значения ( flags.0?true ): они целиком кодируются одним битом в маске, непосредственная сериализация 0x997275b5 не используется, так как избыточна.

Представить такой bitfield в Go можно довольно просто:

Это поле сериализуется как обычный uint32.

А непосредственное использование будет таким:

Криптографические ключи

Еще одим специальным случаем будут типы int128 и int256:

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

Таким образом, сериализуются они очень просто:

Самым сложным было понять, как конвертировать значения между ними и big.Int.

MTProto использует big-endian для этих значений по причине аналогичного поведения в OpenSSL. В Go big.Int поступает так же.

Получается, можно просто использовать SetBytes для чтения и FillBytes для записи:

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

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

Для справки, на текущий момент генерируется около 180K SLOC из схем телеграма (api, mtproto, секретные чаты).

Хочу поблагодарить tdakkota и zweihander за их неоценимый вклад в развитие проекта! Без вас было бы очень тяжело.

Источник

Опционалы в Swift

Несмотря на некоторый опыт в мобильной разработке (в том числе с применением Swift), регулярно на почве свифтовых опционалов возникали ситуации, когда я знал что нужно делать, но не совсем внятно представлял, почему именно так. Приходилось отвлекаться и углубляться в документацию — количество «заметок на полях» пополнялось с удручающей периодичностью. В определенный момент они достигли критической массы, и я решил упорядочить их в едином исчерпывающем руководстве. Материал получился довольно объемным, поскольку предпринята попытка раскрыть тему максимально подробно. Статья будет полезна как начинающим Swift-разработчикам, так и матерым профессионалам из мира Objective-C — есть ненулевая вероятность, что и последние найдут для себя что-то новое. А если не найдут, то добавят свое новое в комментарии, и всем будет польза.

Что такое Optionals?

Optionals (опционалы) — это удобный механизм обработки ситуаций, когда значение переменной может отсутствовать. Значение будет использовано, только если оно есть.

Зачем нужны Optionals, когда есть проверка на nil?

Во-первых, проверка на равенство/неравенство nil применима только к nullable-типам и не применима к примитивным типам, структурам и перечислениям. Для обозначения отсутсвия значения у переменной примитивного типа приходится вводить спецзначения, такие как NSNotFound.

NSNotFound не только нужно рассматривать как спецзначение, но и следить, чтобы оно не входило в множество допустимых значений переменной. Ситуация усложняется еще и тем, что NSNotFound считается равным NSIntegerMax, т.е. может иметь разные значения для разных (32-bit/64-bit) платформ. Это значит, что NSNotFound нельзя напрямую записывать в файлы и архивы или использовать в Distributed Objects.

Соответственно, пользователь этой переменной должен учитывать, что спецзначения возможны. В Swift даже примитивный тип можно использовать в опциональном стиле, т.е явным образом указывать на то, что значения может не быть.

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

В документации значение по умолчанию в случае отсутсвия явного присвоения не упоминается, но сказано, что опционал represents either a wrapped value or nil, the absence of a value. Если опциональная переменная объявлена без явного присвоения (какое-либо Some не присваивалось), то логично следует, что неявно присваивается None — третьего «неинициализрованного» состояния у опционалов нет.

По факту опционал представляет собой системное перечисление:

Перечисление Optional имеет два конструктора. Первый конструктор init(_ some: Wrapped) принимает на вход значение соответсвующего типа, т.е. следующие записи эквивалентны:

Второй конструктор init(nilLiteral: ()) является реализацией протокола ExpressibleByNilLiteral

что логично, поскольку преобразование пустого кортежа Void () в nil несколько неочевидно.

Вместо этого конструктора следует использовать

или вообще не использовать явное присвоение

поскольку nil будет присвоен по умолчанию.

Идиомы использования

Нет особого смысла использовать обычное перечисление с двумя состояниями. Вполне можно реализовать подобный механизм самостоятельно: создать enum c двумя состояниями и конструкторами для соответствующих значений, добавить какой-нибудь постфиксный оператор для Force Unwrapping (например, как это сделано здесь), добавить возможность сравнения с nil или вообще придумать «свой» nil и т.д. Опционалы должны быть интегрированы непосредственно в сам язык, чтобы их использование было естественным, не чужеродным. Разумеется, можно рассматривать такую интеграцию как «синтаксический сахар», однако языки высокого уровня для того и существуют, чтобы писать (и читать) код на них было легко и приятно. Использование опционалов в Swift подразумевает ряд идиом или особых языковых конструкций, которые помогают уменьшить количество ошибок и сделать код более лаконичным. К таким идиомам относятся Implicit Unwrapping, Optional Chaining, Nil-Coalescing и Optional Binding.

Implicit unwrapping

В вызове sayHello(times: my_variable2) извлечение значения 42 из my_variable2 все равно осуществляется, только неявно. Использование неявно извлекаемых опционалов делает код более удобным для чтения — нет восклицательных знаков, которые отвлекают внимание (вероятно, читающего код будет беспокоить использование Force Unwrapping без предварительной проверки). На практике это скорее анти-паттерн, увеличивающий вероятность ошибки. Неявно извлекаемый опционал заставляет компилятор «закрыть глаза» на то, что опционал используется в неопциональном контексте. Ошибка, которая может быть выявлена во время компиляции (вызов sayHello(times: my_variable1) ), проявится только в рантайме (вызов sayHello(times: my_variable3) ). Явный код всегда лучше неявного. Логично предположить, что такое снижение безопасности кода требуется не только ради устранения восклицательных знаков, и это действительно так.

Неявно извлекаемые опционалы позволяют использовать self в конструкторе для иницализации свойств и при этом:

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

Optional Chaining

Nil-Coalescing

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

Optional Binding и приведение типов

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

Таким образом, оператор опционального приведения as? порождает опционал, который часто используется в связке с Optional Binding:

map и flatMap

Методы map и flatMap условно можно отнести к идиомам Swift, потому что они определены в системном перечислении Optional:

Опционалы и обработка исключений

Опционалы и Objective-C

Трансляция из Objective-C в Swift осуществлятся по следующим правилам:

Правила передачи опционалов из Swift в Objective-C несколько проще:

Резюме, синтаксис

Заключение

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

Факт отсутствия данных должен обрабатываться отдельной сущностью, внешней по отношению к самим данным. В С++ или Java в область допустимых значений указателя включено специальный «адрес», обозначающий отсутствие адресата. «Правильный» указатель не может существовать без адресата, следовательно, не может «осознать» отсутствие адресата. Даже человеку, т.е. довольно сложной системе, приходится довольстоваться аксиомой Cogito, ergo sum (лат. — «Мыслю, следовательно существую»). У человека нет достоверных признаков собственного бытия или небытия, но у внешних по отношению к человеку сущностей эти критерии есть. В Swift такой внешней сущностью является опционал.

Дополнительные материалы

UPD: (by Alexander Zimin) Конструктор init(nilLiteral: ()) напрямую вызвать на самом деле можно:

Тем не менее, в документации от Apple не рекомендуется это делать.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *