Как хранить «неизвестные» и «отсутствующие» значения в переменной, сохраняя при этом разницу между «неизвестными» и «отсутствующими»?

57

Считайте это «академическим» вопросом. Мне было интересно время от времени избегать значений NULL, и это пример, когда я не могу найти удовлетворительное решение.


Давайте предположим, что я храню измерения там, где в некоторых случаях измерение, как известно, невозможно (или отсутствует). Я хотел бы сохранить это «пустое» значение в переменной, избегая NULL. В других случаях значение может быть неизвестно. Таким образом, имея измерения для определенного периода времени, запрос об измерении в течение этого периода времени может вернуть 3 вида ответов:

  • Фактическое измерение в это время (например, любое числовое значение, включая 0)
  • А «отсутствует» / «пустое» значение (то есть, измерение было сделано, и значение известно пустым в этой точке).
  • Неизвестное значение (т. Е. В этой точке измерения не проводились. Оно может быть пустым, но может быть и любым другим значением).

Важное уточнение:

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

Я хотел бы иметь возможность писать код, избегая проверок NULL, например (псевдокод):

>>> value = get_measurement()  # returns `2`
>>> print(value * 2)
4

>>> value = get_measurement()  # returns `Empty()`
>>> print(value * 2)
Empty()

>>> value = get_measurement()  # returns `Unknown()`
>>> print(value * 2)
Unknown()

Обратите внимание, что ни один из printоператоров не вызвал исключений (так как NULL не использовались). Таким образом, пустые и неизвестные значения будут распространяться по мере необходимости, и проверка того, является ли значение на самом деле «неизвестным» или «пустым», может быть отложена до момента, когда это действительно необходимо (например, хранение / сериализация значения где-либо).


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

exhuma
источник
19
Почему вы хотите различать «измерение выполнено, но пустое значение» и «не измерено»? На самом деле, что означает «измерение выполнено, но пустое значение»? Датчик не смог выдать правильное значение? В таком случае, как это отличается от «неизвестного»? Вы не сможете вернуться назад во времени и получить правильное значение.
DaveG
3
@DaveG Предположим, выборка количества процессоров на сервере. Если сервер выключен или был удален, это значение просто не существует. Это будет измерение, которое не имеет никакого смысла (возможно, «пропущено» / «пусто» - не лучшие термины). Но ценность, как известно, бессмысленна. Если сервер существует, но процесс извлечения значения завершается сбоем, измерение его является действительным, но завершается неудачей, в результате чего получается «неизвестное» значение.
exhuma
2
@ exhuma Я бы тогда описал это как "не применимо".
Винсент
6
Из любопытства, какие измерения вы проводите, когда «пустое» не просто равно нулю какой-либо шкалы? «Неизвестно» / «отсутствует». Я вижу, что это полезно, например, если датчик не подключен или если исходный вывод датчика является мусором по той или иной причине, но «пустой» в каждом случае, о котором я могу думать, может быть более последовательным представлен как 0, []или {}(скаляр 0, пустой список и пустая карта соответственно). Кроме того, это «отсутствующее» / «неизвестное» значение в основном именно то null, для чего оно - оно означает, что там может быть объект, но его нет.
Ник Хартли
7
Какое бы решение вы ни использовали для этого, обязательно спросите себя, не страдает ли оно от тех же проблем, что и те, из-за которых вы изначально хотели устранить NULL.
Рэй

Ответы:

85

Обычный способ сделать это, по крайней мере с функциональными языками, состоит в использовании дискриминационного объединения. Тогда это значение, которое является одним из допустимых значений int, значением, которое обозначает «отсутствующий», или значением, которое обозначает «неизвестный». В F # это может выглядеть примерно так:

type Measurement =
    | Reading of value : int
    | Missing
    | Unknown of value : RawData

Тогда Measurementзначением будет a Reading, со значением int, или a Missing, или a Unknownс необработанными данными, как value(если требуется).

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

Дэвид Арно
источник
7
Вы можете делать типы сумм на ОО-языках, но есть немало проблем, чтобы заставить их работать stackoverflow.com/questions/3151702/…
jk.
11
«[В языках нефункциональных языков] этот шаблон вряд ли будет вам полезен» - это довольно распространенный шаблон в ООП. GOF имеет разновидность этого шаблона, и языки, такие как C ++, предлагают нативные конструкции для его кодирования.
Конрад Рудольф
14
@jk. Да, они не учитываются (я думаю, что так и есть; в этом сценарии они просто очень плохие из-за отсутствия безопасности). Я имел ввиду std::variant(и его духовных предшественников).
Конрад Рудольф
2
@ Иван Нет, он говорит: «Измерение - это тип данных, который… или…».
Конрад Рудольф
2
@DavidArno Ну, даже без DU есть «каноническое» решение для этого в ООП, которое заключается в том, чтобы иметь суперкласс значений с подклассами для действительных и недействительных значений. Но это, вероятно, заходит слишком далеко (и на практике кажется, что большинство кодовых баз избегают полиморфизма подкласса в пользу флага для этого, как показано в других ответах).
Конрад Рудольф
58

Если вы еще не знаете, что такое монада, сегодня будет отличный день для изучения. У меня есть небольшое введение для программистов OO здесь:

https://ericlippert.com/2013/02/21/monads-part-one/

Ваш сценарий является небольшим расширением «возможно, монада», также известный как Nullable<T>в C # и Optional<T>на других языках.

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

abstract class Measurement<T> { ... }

а затем три подкласса:

final class Unknown<T> : Measurement<T> { ... a singleton ...}
final class Empty<T> : Measurement<T> { ... a singleton ... }
final class Actual<T> : Measurement<T> { ... a wrapper around a T ...}

Нам нужна реализация Bind:

abstract class Measurement<T>
{ 
    public Measurement<R> Bind(Func<T, Measurement<R>> f)
  {
    if (this is Unknown<T>) return Unknown<R>.Singleton;
    if (this is Empty<T>) return Empty<R>.Singleton;
    if (this is Actual<T>) return f(((Actual<T>)this).Value);
    throw ...
  }

Из этого вы можете написать эту упрощенную версию Bind:

public Measurement<R> Bind(Func<A, R> f) 
{
  return this.Bind(a => new Actual<R>(f(a));
}

И теперь вы сделали. У вас есть Measurement<int>в руке. Вы хотите удвоить это:

Measurement<int> m = whatever;
Measurement<int> doubled = m.Bind(a => a * 2);
Measurement<string> asString = m.Bind(a => a.ToString());

И следуй логике; если mесть , Empty<int>то asStringесть Empty<String>, отлично.

Точно так же, если мы имеем

Measurement<int> First()

а также

Measurement<double> Second(int i);

тогда мы можем объединить два измерения:

Measurement<double> d = First().Bind(Second);

и снова, если First()есть, Empty<int>то dесть Empty<double>и так далее.

Ключевым шагом является правильная операция связывания . Подумай об этом.

Эрик Липперт
источник
4
Монады (к счастью) гораздо проще использовать, чем понимать. :)
Гуран
11
@leftaroundabout: Именно потому, что я не хотел входить в это разграничение волос; как отмечается в оригинальном постере, многим людям не хватает уверенности в том, что касается работы с монадами. Жаргон-нагруженные характеристики теории категорий простых операций препятствуют развитию чувства уверенности и понимания.
Эрик Липперт
2
Итак, ваш совет - заменить Nullна Nullable+ некоторый шаблонный код? :)
Эрик Думинил
3
@ Клод: Вы должны прочитать мой учебник. Монада - это универсальный тип, который следует определенным правилам и дает возможность связать воедино цепочку операций, поэтому в данном случае Measurement<T>это монадический тип.
Эрик Липперт
5
@daboross: Хотя я согласен с тем, что монады с сохранением состояния являются хорошим способом введения монад, я не думаю, что несение состояния - это то, что характеризует монаду. Я думаю о том факте, что вы можете связать воедино последовательность функций, это неотразимая вещь; Statefulness это просто деталь реализации.
Эрик Липперт
18

Я думаю, что в этом случае было бы полезно использовать вариант с шаблоном Null Object:

public class Measurement
{
    private int value;
    private bool isUnknown = false;
    private bool isMissing = false;

    private Measurement() { }
    public Measurement(int value) { this.value = value; }

    public int Value {
        get {
            if (!isUnknown && !isMissing)
            {
                return this.value;
            }
            throw new SomeException("...");
        }                   
    }

    public static readonly Measurement Unknown = new Measurement
    {
        isUnknown = true
    };

    public static readonly Measurement Missing = new Measurement
    {
        isMissing = true
    };
}

Вы можете превратить его в структуру, переопределить Equals / GetHashCode / ToString, добавить неявные преобразования из или в int, и, если вам нужно поведение, подобное NaN, вы также можете реализовать свои собственные арифметические операторы, например: Measurement.Unknown * 2 == Measurement.Unknown,

Тем не менее, C # Nullable<int>реализует все это, с единственной оговоркой, что вы не можете различать различные типы nulls. Я не Java-человек, но я понимаю, что Java OptionalIntпохожа, и другие языки, вероятно, имеют свои собственные средства для представления Optionalтипа.

Мацей Стаховский
источник
6
Самая распространенная реализация этого шаблона включает наследование. Может быть случай для двух подклассов: MissingMeasurement и UnknownMeasurement. Они могут реализовывать или переопределять методы в родительском классе Measurement. +1
Грег Бургхардт
2
Не является ли смыслом шаблона нулевого объекта то, что вы не ошибаетесь с недопустимыми значениями, а скорее ничего не делаете?
Крис Волерт
2
@ChrisWohlert в этом случае у объекта на самом деле нет никаких методов, кроме метода получения Value, который обязательно должен завершиться сбоем, так как вы не можете конвертировать Unknownобратно в int. Если бы измерение имело, скажем, SaveToDatabase()метод, то хорошая реализация, вероятно, не будет выполнять транзакцию, если текущий объект является нулевым объектом (либо путем сравнения с одноэлементным кодом, либо с помощью переопределения метода).
Мачей Стаховский
3
@MaciejStachowski Да, я не говорю, что это ничего не должно делать, я говорю, что Null Object Pattern не очень подходит. Ваше решение может быть в порядке, но я бы не назвал его « Шаблон нулевого объекта» .
Крис Волерт
14

Если вы буквально ДОЛЖНЫ использовать целое число, то есть только одно возможное решение. Используйте некоторые из возможных значений как «магические числа», которые означают «отсутствующие» и «неизвестные»

например, 2 147 483 647 и 2 147 483 646

Если вам просто нужно int для «реальных» измерений, то создайте более сложную структуру данных

class Measurement {
    public bool IsEmpty;
    public bool IsKnown;
    public int Value {
        get {
            if(!IsEmpty && IsKnown) return _value;
            throw new Exception("NaN");
            }
        }
}

Важное уточнение:

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

public static Measurement operator+ (Measurement a, Measurement b) {
    if(a.IsEmpty) { return b; }
    ...etc
}
Ewan
источник
10
@KakturusOption<Option<Int>>
Берги
5
@Bergi Вы не можете даже думать, что это даже отдаленно приемлемо ..
BlueRaja - Дэнни Пфлугхофт
8
@ BlueRaja-DannyPflughoeft На самом деле он очень хорошо подходит под описание OP, которое также имеет вложенную структуру. Чтобы стать приемлемым, мы, конечно, ввели бы правильный псевдоним типа (или «новый тип»), но type Measurement = Option<Int>для результата, который был целым или пустым, все в порядке, как и Option<Measurement>для измерения, которое могло быть выполнено или нет ,
Берги
7
@arp "Целые числа рядом с NaN"? Не могли бы вы объяснить, что вы подразумеваете под этим? Кажется несколько нелогичным сказать, что число «близко» к самому понятию чего-то, что не является числом.
Ник Хартли
3
@Nic Hartley В нашей системе группа из того, что «естественно» было бы наименьшим возможным отрицательным целым числом, была зарезервирована как NaN. Мы использовали это пространство для кодирования различных причин, по которым эти байты представляли собой нечто иное, чем допустимые данные. (это было десятилетия назад, и я, возможно, размыл некоторые детали, но определенно был набор битов, которые можно было бы поместить в целочисленное значение, чтобы оно выдавало NaN, если вы пытались сделать с ним математику.
arp
11

Если переменные числа с плавающей точкой, IEEE754 (точка стандартной плавающей номер , который поддерживается большинством современных процессоров и языков) имеет свою спину: это малоизвестная особенность, но стандарт определяет не один, а целая семья из Значения NaN (не числа), которые могут использоваться для произвольных значений, определенных приложением. Например, в плавающих с одинарной точностью у вас есть 22 свободных бита, которые вы можете использовать для различения 2 ^ {22} типов недопустимых значений.

Обычно программные интерфейсы предоставляют только один из них (например, Numpy's nan); Я не знаю, есть ли встроенный способ для генерации других, кроме явной битовой манипуляции, но это всего лишь вопрос написания пары низкоуровневых подпрограмм. (Вам также понадобится один, чтобы отличить их друг от друга, потому что по замыслу a == bвсегда возвращает false, когда один из них является NaN.)

Использовать их лучше, чем изобретать свое собственное «магическое число», чтобы сигнализировать о недействительных данных, потому что они распространяются правильно и сигнализируют о недействительности: например, вы не рискуете выстрелить себе в ногу, если используете average()функцию и забыли проверить ваши особые ценности.

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

Федерико Полони
источник
6

Вслед за ответ Дэвида Арно , вы можете сделать что - то вроде размеченного объединения в ООП, и в объектно-функциональной стиле , такие , как обеспечиваемая Scala, с помощью Java 8 функциональных типов или Java FP библиотеки , такие как Vavr или фуги он чувствует себя довольно естественно написать что-то вроде:

var value = Measurement.of(2);
out.println(value.map(x -> x * 2));

var empty = Measurement.empty();
out.println(empty.map(x -> x * 2));

var unknown = Measurement.unknown();
out.println(unknown.map(x -> x * 2));

печать

Value(4)
Empty()
Unknown()

( Полная реализация как суть .)

Язык FP или библиотека предоставляют другие инструменты, такие как Try(aka Maybe) (объект, который содержит либо значение, либо ошибку) и Either(объект, который содержит либо значение успеха, либо значение ошибки), которые также могут быть использованы здесь.

Дэвид Моулз
источник
2

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

Просто придумывание «второго аромата» нуля не дает нижестоящему набору процессов достаточно информации для получения разумного набора поведений.

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

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

Некоторые шаблоны для обработки этого:

  • Типы сумм
  • Дискриминационные союзы
  • Объекты или структуры, содержащие перечисление, представляющее результат операции, и поле для результата
  • Волшебные струны или магические числа, которых невозможно достичь при нормальной работе
  • Исключения, в языках, на которых это использование является идиоматическим
  • Понимая, что на самом деле нет никакого смысла в разграничении этих двух сценариев и просто использовании null
Железный Гремлин
источник
2

Если бы меня интересовало «сделать что-то», а не элегантное решение, быстрый и грязный взлом состоял бы в простом использовании строк «неизвестно», «отсутствует» и «строковое представление моего числового значения», которые затем были бы конвертируется из строки и используется по мере необходимости. Реализовано быстрее, чем написать это, и, по крайней мере, в некоторых обстоятельствах, вполне адекватно. (Сейчас я формирую пул ставок на количество понижений ...)

mickeyf_supports_Monica
источник
Голосовали за упоминание "что-то сделать".
барбекю
4
Некоторые люди могут заметить, что это переносит большинство тех же проблем, что и использование NULL, а именно, что он просто переключается с необходимости проверок NULL на необходимость проверок «неизвестных» и «пропущенных», но сохраняет сбой во время выполнения для счастливого, тихого повреждения данных для невезучие как единственные индикаторы того, что вы забыли чек. Даже пропущенные NULL-проверки имеют то преимущество, что линтеры могут их перехватить, но это теряет это. Тем не менее, он добавляет различие между «неизвестным» и «отсутствующим», так что там он превосходит NULL ...
8bittree
2

Суть в том, что вопрос кажется следующим: «Как я могу вернуть две несвязанные части информации из метода, который возвращает единственное целое число? Я никогда не хочу проверять свои возвращаемые значения, а нулевые значения плохие, не используйте их».

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

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

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

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

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

А try / catch и handler - это такой же шаблон, как и нулевая проверка, против которой прежде всего возражали.

И если вызывающая сторона не содержит try / catch, то вызывающая сторона должна и так далее.


Наивный второй проход - сказать: «Это измерение. Отрицательные измерения расстояния маловероятны». Так что для некоторого измерения Y, вы можете просто иметь констант для

  • -1 = неизвестно,
  • -2 = невозможно измерить,
  • -3 = отказался отвечать,
  • -4 = известный, но конфиденциальный,
  • -5 = варьируется в зависимости от фазы луны, см. Таблицу 5а,
  • -6 = четырехмерный, измерения приведены в заголовке,
  • -7 = ошибка чтения файловой системы,
  • -8 = зарезервировано для будущего использования,
  • -9 = квадрат / куб, поэтому Y такой же, как X,
  • -10 = - экран монитора, поэтому не используются измерения X, Y: используйте X в качестве диагонали экрана,
  • -11 = записал результаты измерений на обратной стороне квитанции, и она была отмыта до неразборчивости, но я думаю, что это было либо 5, либо 17,
  • -12 = ... ты понял.

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

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

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


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

В этом случае это выглядит хорошим случаем для структуры, которая передает int и обоснование. Опять же, это обоснование может быть просто константой, подобной приведенной выше, но вместо того, чтобы держать оба в одном и том же int, вы сохраняете их как отдельные части структуры. Первоначально, у нас есть правило, что, если обоснование установлено, int не будет установлен. Но мы больше не привязаны к этому правилу; мы можем предоставить обоснования для действительных номеров, если это необходимо.

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

Вот где вам нужно исследовать ваши аргументы "не используйте ноль".

Как и исключения, null предназначен для обозначения исключительного состояния.

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

Поэтому каждый раз, когда вы вызываете этот метод, вы должны проверять его возвращаемое значение, однако вы обрабатываете недопустимые значения, будь то внутриполосные или внеполосные, попробуйте / перехватите, проверив структуру для компонента «обоснование», проверив int для магического числа, или проверка int на нулевое значение ...

Альтернативой для обработки умножения выходных данных, которые могут содержать недопустимое значение int и обоснование, например «Моя собака съела это измерение», является перегрузка оператора умножения для этой структуры.

... А затем перегрузите все остальные операторы в вашем приложении, которые могут быть применены к этим данным.

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

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

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

  1. Если у вас есть недопустимые значения, вы не можете избежать проверки этих недопустимых значений в любой точке кода, где вы обрабатываете значения.
  2. Если вы возвращаете что-то кроме int, вы не возвращаете int, поэтому вы не можете рассматривать его как int. Перегрузка оператора позволяет вам притворяться , но это только притворство.
  3. Int с магическими числами (включая NULL, NAN, Inf ...) больше не является int, это структура бедняка.
  4. Исключение пустых значений не сделает код более устойчивым, оно просто скроет проблемы с целыми числами или переместит их в сложную структуру обработки исключений.
Деви Морган
источник
1

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

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

Другие языки имеют охранные предложения (например, Swift или Ruby), а Ruby имеет условный досрочный возврат.

Я видел, как это решается в Python несколькими способами:

  • со структурой данных обертки, так как числовая информация обычно собирается для объекта и имеет время измерения. Оболочка может переопределять магические методы, например, __mult__чтобы не возникало никаких исключений при появлении значений Unknown или Missing. Numpy и панды могут иметь такую ​​способность в них.
  • со значением часового (как ваш Unknownили -1 / -2) и оператор if
  • с отдельным логическим флагом
  • с ленивой структурой данных - ваша функция выполняет некоторую операцию над структурой, а затем возвращает, самая внешняя функция, которой требуется фактический результат, оценивает ленивую структуру данных
  • с ленивым конвейером операций - аналогично предыдущему, но этот можно использовать для набора данных или базы данных
noɥʇʎԀʎzɐɹƆ
источник
1

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

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

Решение 1: не избегайте нулевых проверок

Missingможет быть представлен как math.nan
Unknownможет быть представлен какNone

Если у вас есть более одного значения, вы можете filter()применять операцию только к значениям, которые не являются Unknownили Missing, или к любым значениям, которые вы хотите игнорировать для функции.

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


Решение 2: использовать декоратор, который ловит исключения

В этом случае Missingможет подняться MissingExceptionи Unknownможет подняться UnknownExceptionпри выполнении операций над ним.

@suppressUnknown(value=Unknown) # if an UnknownException is raised, return this value instead
@suppressMissing(value=Missing)
def sigmoid(value):
    ...

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

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

sigmoidМожно по-прежнему вызывать sin, даже если он не ожидает Missingили Unknown, поскольку sigmoidдекоратор поймает исключение.

noɥʇʎԀʎzɐɹƆ
источник
1
Интересно, какой смысл публиковать два ответа на один и тот же вопрос (это ваш предыдущий ответ , что-то не так с ним?)
Гнат
@gnat Этот ответ дает обоснование, почему это не должно быть сделано так, как показывает автор, и я не хотел проходить через сложность объединения двух ответов с разными идеями - просто проще написать два ответа, которые можно читать независимо , Я не понимаю, почему вы так заботитесь о чьих-то безобидных рассуждениях.
noɥʇʎԀʎzɐɹƆ
0

Предположим, выборка количества процессоров на сервере. Если сервер выключен или был удален, это значение просто не существует. Это будет измерение, которое не имеет никакого смысла (возможно, «пропущено» / «пусто» - не лучшие термины). Но ценность, как известно, бессмысленна. Если сервер существует, но процесс извлечения значения завершается сбоем, измерение его является действительным, но завершается неудачей, в результате чего получается «неизвестное» значение.

Оба из них звучат как условия ошибки, поэтому я бы сказал, что лучший вариант здесь - просто сразу get_measurement()выбросить оба из них как исключения (например, DataSourceUnavailableExceptionили SpectacularFailureToGetDataException, соответственно). Затем, если возникнет какая-либо из этих проблем, код для сбора данных может немедленно отреагировать на него (например, повторить попытку в последнем случае) и get_measurement()должен вернуть только intв том случае, если он может успешно получить данные из данных. источник - и вы знаете, что intэто действительно.

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

TheHansinator
источник
0

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

  • Высшее приходит неизвестно .
  • Затем перед использованием значения сначала необходимо уточнить пустое значение .
  • Последнее приходит значение для расчета.

Уродливый (из-за неудачной абстракции), но полностью работоспособный (на Java):

Optional<Optional<Integer>> unknowableValue;

unknowableValue.ifPresent(emptiableValue -> ...);
Optional<Integer> emptiableValue = unknowableValue.orElse(Optional.empty());

emptiableValue.ifPresent(value -> ...);
int value = emptiableValue.orElse(0);

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

На самом деле: В пустые / отсутствующие и неизвестные * не-ценности кажутся скорее частью какоголибо процесса, то некоторые производства трубопровода. Как ячейки электронной таблицы Excel с формулами, ссылающимися на другие ячейки. Там можно подумать о хранении контекстных лямбд. Изменение ячейки переоценило бы все рекурсивно зависимые ячейки.

В этом случае значение int будет получено поставщиком int. Пустое значение даст поставщику int, выбрасывающему пустое исключение или вычисляющему пустое (рекурсивно вверх). Ваша основная формула соединит все значения и, возможно, также вернет пустое значение (значение / исключение). Неизвестное значение отключило бы оценку, вызвав исключение.

Значения, вероятно, будут заметны, как свойство, связанное с java, уведомляя слушателей об изменениях.

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

Joop Eggen
источник
0

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

  • если мы измеряем только длины виджетов, то не важно различать «сбой датчика», «отключение питания» или «сбой сети» (хотя «переполнение чисел» действительно передает информацию)

  • но, например, при анализе данных или опросе, когда респонденты спрашивают, например, об их доходе или ВИЧ-статусе, результат «Неизвестно» отличается от «Отклонить от ответа», и вы можете видеть, что наши предыдущие предположения о том, как вменять эти данные, будут иметь тенденцию отличаться от первого. Таким образом, такие языки, как SAS, поддерживают несколько разных типов NA; язык R этого не делает, но пользователям очень часто приходится взламывать его; NA в разных точках конвейера могут использоваться для обозначения совершенно разных вещей.

  • также есть случай, когда у нас есть несколько переменных NA для одной записи («множественное вменение»). Пример: если я не знаю какого-либо возраста, почтового индекса, уровня образования или дохода человека, его труднее вменять.

Что касается того, как вы представляете различные типы NA в языках общего назначения, которые их не поддерживают, обычно люди взламывают такие вещи, как NaN с плавающей точкой (требуется преобразование целых чисел), перечисления или часовые (например, 999 или -1000) для целых чисел или категориальные значения. Обычно нет очень чистого ответа, извините.

SMCI
источник
0

R имеет встроенную поддержку отсутствующего значения. https://medium.com/coinmonks/dealing-with-missing-data-using-r-3ae428da2d17

Изменить: потому что я был понижен голосом, я собираюсь объяснить немного.

Если вы собираетесь заниматься статистикой, я рекомендую вам использовать язык статистики, такой как R, потому что R пишется статистиками для статистиков. Отсутствие ценностей - такая большая тема, что они преподают вам целый семестр. И есть большие книги только о пропущенных ценностях.

Однако вы можете пометить пропущенные данные, например, точку или «пропущенные» или что-то еще. В R вы можете определить, что вы подразумеваете под отсутствующим. Вам не нужно конвертировать их.

Обычный способ определить отсутствующее значение - пометить их как NA.

x <- c(1, 2, NA, 4, "")

Затем вы можете увидеть, какие значения отсутствуют;

is.na(x)

И тогда результат будет;

FALSE FALSE  TRUE FALSE FALSE

Как видите "", не пропал. Вы можете угрожать ""как неизвестные. И NAотсутствует.

Илхан
источник
@ Халк, какие еще функциональные языки поддерживают пропущенные значения? Даже если они поддерживают пропущенные значения, я уверен, что вы не можете заполнить их статистическими методами только в одной строке кода.
Ильхан
-1

Есть ли причина, по которой функциональность *оператора не может быть изменена?

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

Вы бы тогда быть в состоянии иметь подобную empty()/ unknown()функциональность по всему вашему проекту.

Эдвард
источник
4
Это означает, что вам придется перегружать всех операторов
труба