Считайте это «академическим» вопросом. Мне было интересно время от времени избегать значений 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, но я обнаружил, что их избегание может сделать код намного более надежным в некоторых случаях.
0
,[]
или{}
(скаляр 0, пустой список и пустая карта соответственно). Кроме того, это «отсутствующее» / «неизвестное» значение в основном именно тоnull
, для чего оно - оно означает, что там может быть объект, но его нет.Ответы:
Обычный способ сделать это, по крайней мере с функциональными языками, состоит в использовании дискриминационного объединения. Тогда это значение, которое является одним из допустимых значений int, значением, которое обозначает «отсутствующий», или значением, которое обозначает «неизвестный». В F # это может выглядеть примерно так:
Тогда
Measurement
значением будет aReading
, со значением int, или aMissing
, или aUnknown
с необработанными данными, какvalue
(если требуется).Однако, если вы не используете язык, который поддерживает дискриминационные союзы или их эквивалент, этот шаблон вряд ли будет вам полезен. Таким образом, вы можете, например, использовать класс с полем enum, которое обозначает, какой из трех содержит правильные данные.
источник
std::variant
(и его духовных предшественников).Если вы еще не знаете, что такое монада, сегодня будет отличный день для изучения. У меня есть небольшое введение для программистов OO здесь:
https://ericlippert.com/2013/02/21/monads-part-one/
Ваш сценарий является небольшим расширением «возможно, монада», также известный как
Nullable<T>
в C # иOptional<T>
на других языках.Предположим, у вас есть абстрактный тип для представления монады:
а затем три подкласса:
Нам нужна реализация Bind:
Из этого вы можете написать эту упрощенную версию Bind:
И теперь вы сделали. У вас есть
Measurement<int>
в руке. Вы хотите удвоить это:И следуй логике; если
m
есть ,Empty<int>
тоasString
естьEmpty<String>
, отлично.Точно так же, если мы имеем
а также
тогда мы можем объединить два измерения:
и снова, если
First()
есть,Empty<int>
тоd
естьEmpty<double>
и так далее.Ключевым шагом является правильная операция связывания . Подумай об этом.
источник
Null
наNullable
+ некоторый шаблонный код? :)Measurement<T>
это монадический тип.Я думаю, что в этом случае было бы полезно использовать вариант с шаблоном Null Object:
Вы можете превратить его в структуру, переопределить Equals / GetHashCode / ToString, добавить неявные преобразования из или в
int
, и, если вам нужно поведение, подобное NaN, вы также можете реализовать свои собственные арифметические операторы, например:Measurement.Unknown * 2 == Measurement.Unknown
,Тем не менее, C #
Nullable<int>
реализует все это, с единственной оговоркой, что вы не можете различать различные типыnull
s. Я не Java-человек, но я понимаю, что JavaOptionalInt
похожа, и другие языки, вероятно, имеют свои собственные средства для представленияOptional
типа.источник
Value
, который обязательно должен завершиться сбоем, так как вы не можете конвертироватьUnknown
обратно вint
. Если бы измерение имело, скажем,SaveToDatabase()
метод, то хорошая реализация, вероятно, не будет выполнять транзакцию, если текущий объект является нулевым объектом (либо путем сравнения с одноэлементным кодом, либо с помощью переопределения метода).Если вы буквально ДОЛЖНЫ использовать целое число, то есть только одно возможное решение. Используйте некоторые из возможных значений как «магические числа», которые означают «отсутствующие» и «неизвестные»
например, 2 147 483 647 и 2 147 483 646
Если вам просто нужно int для «реальных» измерений, то создайте более сложную структуру данных
Важное уточнение:
Вы можете достичь требования по математике, перегружая операторы для класса
источник
Option<Option<Int>>
type Measurement = Option<Int>
для результата, который был целым или пустым, все в порядке, как иOption<Measurement>
для измерения, которое могло быть выполнено или нет ,Если переменные числа с плавающей точкой, IEEE754 (точка стандартной плавающей номер , который поддерживается большинством современных процессоров и языков) имеет свою спину: это малоизвестная особенность, но стандарт определяет не один, а целая семья из Значения NaN (не числа), которые могут использоваться для произвольных значений, определенных приложением. Например, в плавающих с одинарной точностью у вас есть 22 свободных бита, которые вы можете использовать для различения 2 ^ {22} типов недопустимых значений.
Обычно программные интерфейсы предоставляют только один из них (например, Numpy's
nan
); Я не знаю, есть ли встроенный способ для генерации других, кроме явной битовой манипуляции, но это всего лишь вопрос написания пары низкоуровневых подпрограмм. (Вам также понадобится один, чтобы отличить их друг от друга, потому что по замыслуa == b
всегда возвращает false, когда один из них является NaN.)Использовать их лучше, чем изобретать свое собственное «магическое число», чтобы сигнализировать о недействительных данных, потому что они распространяются правильно и сигнализируют о недействительности: например, вы не рискуете выстрелить себе в ногу, если используете
average()
функцию и забыли проверить ваши особые ценности.Единственный риск заключается в том, что библиотеки не поддерживают их должным образом, поскольку они являются довольно неясной особенностью: например, библиотека сериализации может «сгладить» их все одинаково
nan
(что выглядит эквивалентным для большинства целей).источник
Вслед за ответ Дэвида Арно , вы можете сделать что - то вроде размеченного объединения в ООП, и в объектно-функциональной стиле , такие , как обеспечиваемая Scala, с помощью Java 8 функциональных типов или Java FP библиотеки , такие как Vavr или фуги он чувствует себя довольно естественно написать что-то вроде:
печать
( Полная реализация как суть .)
Язык FP или библиотека предоставляют другие инструменты, такие как
Try
(akaMaybe
) (объект, который содержит либо значение, либо ошибку) иEither
(объект, который содержит либо значение успеха, либо значение ошибки), которые также могут быть использованы здесь.источник
Идеальное решение вашей проблемы будет зависеть от того, почему вы заботитесь о разнице между известным отказом и известным ненадежным измерением и тем, какие процессы ниже по течению вы хотите поддерживать. Обратите внимание, что «последующие процессы» для этого случая не исключают людей-операторов или коллег-разработчиков.
Просто придумывание «второго аромата» нуля не дает нижестоящему набору процессов достаточно информации для получения разумного набора поведений.
Если вместо этого вы полагаетесь на контекстные предположения об источнике плохого поведения, создаваемого нижестоящим кодом, я бы назвал эту плохую архитектуру.
Если вы знаете достаточно, чтобы различать причину сбоя и сбой без известной причины, и эта информация будет использоваться для информирования о будущем поведении, вам следует передавать эти знания ниже по течению или обрабатывать их в оперативном режиме.
Некоторые шаблоны для обработки этого:
null
источник
Если бы меня интересовало «сделать что-то», а не элегантное решение, быстрый и грязный взлом состоял бы в простом использовании строк «неизвестно», «отсутствует» и «строковое представление моего числового значения», которые затем были бы конвертируется из строки и используется по мере необходимости. Реализовано быстрее, чем написать это, и, по крайней мере, в некоторых обстоятельствах, вполне адекватно. (Сейчас я формирую пул ставок на количество понижений ...)
источник
Суть в том, что вопрос кажется следующим: «Как я могу вернуть две несвязанные части информации из метода, который возвращает единственное целое число? Я никогда не хочу проверять свои возвращаемые значения, а нулевые значения плохие, не используйте их».
Давайте посмотрим на то, что вы хотите передать. Вы передаете либо int, либо non-int обоснование того, почему вы не можете дать int. Вопрос утверждает, что будет только две причины, но любой, кто когда-либо делал перечисление, знает, что любой список будет расти. Сфера, чтобы указать другие обоснования просто имеет смысл.
Сначала, похоже, это может быть хорошим поводом для создания исключения.
Когда вы хотите сообщить вызывающей стороне что-то особенное, чего нет в типе возврата, исключения часто являются подходящей системой: исключения не только для состояний ошибок, и позволяют вам возвращать много контекста и обоснования, чтобы объяснить, почему вы просто можете не сегодня.
И это ЕДИНСТВЕННАЯ система, которая позволяет вам возвращать гарантированно действительные целочисленные значения и гарантирует, что каждый оператор int и метод, принимающий целочисленные значения, могут принимать возвращаемое значение этого метода без необходимости проверять недопустимые значения, такие как нулевые или магические значения.
Но исключения на самом деле являются только верным решением, если, как следует из названия, это исключительный случай, а не нормальный ход бизнеса.
А try / catch и handler - это такой же шаблон, как и нулевая проверка, против которой прежде всего возражали.
И если вызывающая сторона не содержит try / catch, то вызывающая сторона должна и так далее.
Наивный второй проход - сказать: «Это измерение. Отрицательные измерения расстояния маловероятны». Так что для некоторого измерения Y, вы можете просто иметь констант для
Именно так это делается во многих старых C-системах и даже в современных системах, где есть подлинное ограничение для int, и вы не можете связать его со структурой или монадой какого-либо типа.
Если измерения могут быть отрицательными, тогда вы просто увеличиваете свой тип данных (например, long int) и получаете магические значения выше диапазона int, и в идеале начинаете с некоторого значения, которое будет четко отображаться в отладчике.
Однако есть веские причины иметь их в качестве отдельной переменной, а не просто иметь магические числа. Например, строгая типизация, ремонтопригодность и соответствие ожиданиям.
В нашей третьей попытке мы рассмотрим случаи, когда обычным делом является использование не-int-значений. Например, если коллекция этих значений может содержать несколько нецелых записей. Это означает, что обработчик исключений может быть неправильным подходом.
В этом случае это выглядит хорошим случаем для структуры, которая передает int и обоснование. Опять же, это обоснование может быть просто константой, подобной приведенной выше, но вместо того, чтобы держать оба в одном и том же int, вы сохраняете их как отдельные части структуры. Первоначально, у нас есть правило, что, если обоснование установлено, int не будет установлен. Но мы больше не привязаны к этому правилу; мы можем предоставить обоснования для действительных номеров, если это необходимо.
В любом случае, каждый раз, когда вы звоните, вам все еще нужен шаблон, чтобы проверить обоснование, чтобы увидеть, является ли int действительным, а затем вытащить и использовать часть int, если это позволяет обоснование.
Вот где вам нужно исследовать ваши аргументы "не используйте ноль".
Как и исключения, null предназначен для обозначения исключительного состояния.
Если вызывающая сторона вызывает этот метод и полностью игнорирует часть «логического обоснования» структуры, ожидая число без какой-либо обработки ошибок, и получает ноль, то она будет обрабатывать ноль как число и ошибаться. Если он получит магическое число, он будет воспринимать его как число и будет ошибочным. Но если он получит нулевое значение, он упадет , как, черт побери, должно быть.
Поэтому каждый раз, когда вы вызываете этот метод, вы должны проверять его возвращаемое значение, однако вы обрабатываете недопустимые значения, будь то внутриполосные или внеполосные, попробуйте / перехватите, проверив структуру для компонента «обоснование», проверив int для магического числа, или проверка int на нулевое значение ...
Альтернативой для обработки умножения выходных данных, которые могут содержать недопустимое значение int и обоснование, например «Моя собака съела это измерение», является перегрузка оператора умножения для этой структуры.
... А затем перегрузите все остальные операторы в вашем приложении, которые могут быть применены к этим данным.
... А затем перегрузите все методы, которые могут принимать целочисленные значения.
... И все эти перегрузки должны по- прежнему содержать проверки на недопустимые целые числа, просто чтобы вы могли обрабатывать возвращаемый тип этого одного метода, как если бы он всегда был действительным int в момент, когда вы вызываете его.
Таким образом, первоначальная предпосылка ложна по-разному:
источник
Я не понимаю предпосылку вашего вопроса, но вот ответ номинальной стоимости. Для Пропавших или Пустых, вы можете сделать
math.nan
(не число). Вы можете выполнять любые математические операции надmath.nan
и она останетсяmath.nan
.Вы можете использовать
None
(ноль Python) для неизвестного значения. Вы не должны манипулировать неизвестным значением в любом случае, и некоторые языки (Python не является одним из них) имеют специальные нулевые операторы, так что операция выполняется только в том случае, если значение ненулевое, в противном случае значение остается нулевым.Другие языки имеют охранные предложения (например, Swift или Ruby), а Ruby имеет условный досрочный возврат.
Я видел, как это решается в Python несколькими способами:
__mult__
чтобы не возникало никаких исключений при появлении значений Unknown или Missing. Numpy и панды могут иметь такую способность в них.Unknown
или -1 / -2) и оператор ifисточник
То, как значение хранится в памяти, зависит от языка и деталей реализации. Я думаю, что вы имеете в виду, как объект должен вести себя с программистом. (Вот как я читаю вопрос, скажите мне, если я ошибаюсь.)
Вы уже предложили ответ на этот вопрос в своем вопросе: используйте свой собственный класс, который принимает любую математическую операцию и возвращает себя, не вызывая исключения. Вы говорите, что хотите этого, потому что хотите избежать нулевых проверок.
Решение 1: не избегайте нулевых проверок
Missing
может быть представлен какmath.nan
Unknown
может быть представлен какNone
Если у вас есть более одного значения, вы можете
filter()
применять операцию только к значениям, которые не являютсяUnknown
илиMissing
, или к любым значениям, которые вы хотите игнорировать для функции.Я не могу представить сценарий, в котором вам нужна проверка нуля для функции, которая действует на один скаляр. В этом случае хорошо бы принудительно выполнить нулевые проверки.
Решение 2: использовать декоратор, который ловит исключения
В этом случае
Missing
может поднятьсяMissingException
иUnknown
может поднятьсяUnknownException
при выполнении операций над ним.Преимущество этого подхода заключается в том, что свойства
Missing
иUnknown
подавляются только тогда, когда вы явно просите их подавить. Другое преимущество состоит в том, что этот подход самодокументируется: каждая функция показывает, ожидает ли она неизвестного или пропавшего и как функцию.Когда вы вызываете функцию, не ожидающую, что Missing получает Missing, функция немедленно поднимается, показывая вам, где именно произошла ошибка, вместо того, чтобы молча завершаться сбоем и распространять сообщение Missing вверх по цепочке вызовов. То же самое касается Неизвестного.
sigmoid
Можно по-прежнему вызыватьsin
, даже если он не ожидаетMissing
илиUnknown
, посколькуsigmoid
декоратор поймает исключение.источник
Оба из них звучат как условия ошибки, поэтому я бы сказал, что лучший вариант здесь - просто сразу
get_measurement()
выбросить оба из них как исключения (например,DataSourceUnavailableException
илиSpectacularFailureToGetDataException
, соответственно). Затем, если возникнет какая-либо из этих проблем, код для сбора данных может немедленно отреагировать на него (например, повторить попытку в последнем случае) иget_measurement()
должен вернуть толькоint
в том случае, если он может успешно получить данные из данных. источник - и вы знаете, чтоint
это действительно.Если ваша ситуация не поддерживает исключения или не может их широко использовать, то хорошей альтернативой является использование кодов ошибок, которые могут быть возвращены через отдельный вывод
get_measurement()
. Это идиоматический шаблон в C, где фактический вывод хранится во входном указателе, а код ошибки передается обратно в качестве возвращаемого значения.источник
Данные ответы хороши, но все же не отражают иерархическую связь между значением, пустым и неизвестным.
Уродливый (из-за неудачной абстракции), но полностью работоспособный (на Java):
Здесь функциональные языки с хорошей системой типов лучше.
На самом деле: В пустые / отсутствующие и неизвестные * не-ценности кажутся скорее частью какоголибо процесса, то некоторые производства трубопровода. Как ячейки электронной таблицы Excel с формулами, ссылающимися на другие ячейки. Там можно подумать о хранении контекстных лямбд. Изменение ячейки переоценило бы все рекурсивно зависимые ячейки.
В этом случае значение int будет получено поставщиком int. Пустое значение даст поставщику int, выбрасывающему пустое исключение или вычисляющему пустое (рекурсивно вверх). Ваша основная формула соединит все значения и, возможно, также вернет пустое значение (значение / исключение). Неизвестное значение отключило бы оценку, вызвав исключение.
Значения, вероятно, будут заметны, как свойство, связанное с java, уведомляя слушателей об изменениях.
Короче говоря: повторяющаяся модель необходимости значений с дополнительными состояниями пустыми и неизвестными, кажется, указывает на то, что более подходящая модель данных в виде электронных таблиц, таких как связанные свойства.
источник
Да, концепция нескольких разных типов NA существует в некоторых языках; больше в статистических, где это более значимо (а именно: огромное различие между отсутствием при случайном, отсутствием при случайном, отсутствием при случайном ).
если мы измеряем только длины виджетов, то не важно различать «сбой датчика», «отключение питания» или «сбой сети» (хотя «переполнение чисел» действительно передает информацию)
но, например, при анализе данных или опросе, когда респонденты спрашивают, например, об их доходе или ВИЧ-статусе, результат «Неизвестно» отличается от «Отклонить от ответа», и вы можете видеть, что наши предыдущие предположения о том, как вменять эти данные, будут иметь тенденцию отличаться от первого. Таким образом, такие языки, как SAS, поддерживают несколько разных типов NA; язык R этого не делает, но пользователям очень часто приходится взламывать его; NA в разных точках конвейера могут использоваться для обозначения совершенно разных вещей.
Что касается того, как вы представляете различные типы NA в языках общего назначения, которые их не поддерживают, обычно люди взламывают такие вещи, как NaN с плавающей точкой (требуется преобразование целых чисел), перечисления или часовые (например, 999 или -1000) для целых чисел или категориальные значения. Обычно нет очень чистого ответа, извините.
источник
R имеет встроенную поддержку отсутствующего значения. https://medium.com/coinmonks/dealing-with-missing-data-using-r-3ae428da2d17
Изменить: потому что я был понижен голосом, я собираюсь объяснить немного.
Если вы собираетесь заниматься статистикой, я рекомендую вам использовать язык статистики, такой как R, потому что R пишется статистиками для статистиков. Отсутствие ценностей - такая большая тема, что они преподают вам целый семестр. И есть большие книги только о пропущенных ценностях.
Однако вы можете пометить пропущенные данные, например, точку или «пропущенные» или что-то еще. В R вы можете определить, что вы подразумеваете под отсутствующим. Вам не нужно конвертировать их.
Обычный способ определить отсутствующее значение - пометить их как
NA
.Затем вы можете увидеть, какие значения отсутствуют;
И тогда результат будет;
Как видите
""
, не пропал. Вы можете угрожать""
как неизвестные. ИNA
отсутствует.источник
Есть ли причина, по которой функциональность
*
оператора не может быть изменена?Большинство ответов включают какое-либо значение поиска, но в этом случае может быть проще изменить математический оператор.
Вы бы тогда быть в состоянии иметь подобную
empty()
/unknown()
функциональность по всему вашему проекту.источник