Як слід зберігати значення "невідомі" та "відсутні" у змінній, зберігаючи різницю між "невідомим" та "відсутнім"?


57

Вважайте це "академічним" питанням. Мені було цікаво про те, щоб час від часу уникати NULL, і це приклад, коли я не можу знайти задовільне рішення.


Припустимо, що я зберігаю вимірювання там, де іноді відомо, що вимірювання неможливе (або відсутнє). Я хотів би зберегти це "порожнє" значення у змінній, уникаючи NULL. В інших випадках значення може бути невідомим. Отже, маючи вимірювання протягом певного часового періоду, запит про вимірювання протягом цього періоду часу може повернути 3 типи відповідей:

  • Фактичне вимірювання на той час (наприклад, будь-яке числове значення, включаючи 0)
  • Значення "відсутнє" / "порожнє" (тобто було проведено вимірювання, а значення, як відомо, в цій точці порожнє).
  • Невідоме значення (тобто в цьому моменті не проводилося жодне вимірювання. Це може бути порожнім, але може бути і будь-яким іншим значенням).

Важливе уточнення:

Припустимо, що у вас була функція, що get_measurement()повертає одне з "порожніх", "невідомих" та значення типу "ціле число". Маючи числове значення, означає, що певні операції можуть бути виконані на поверненому значенні (множення, ділення, ...), але використання таких операцій на NULL буде руйнувати додаток, якщо його не спіймали.

Я хотів би мати можливість писати код, уникаючи 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, але я виявив, що уникнення їх може зробити код набагато більш надійним у деяких випадках.


19
Чому ви хочете розрізняти "вимірювання зроблено, але порожнє значення" проти "немає вимірювання"? Насправді, що означає "зроблене вимірювання, але порожнє значення"? Чи не вдалося датчику дати дійсне значення? У цьому випадку, чим це відрізняється від "невідомого"? Ви не зможете повернутися назад у часі і отримати правильне значення.
DaveG

3
@DaveG Припустимо отримання кількості процесорів на сервері. Якщо сервер вимкнено або його було замінено, це значення просто не існує. Це буде вимірювання, яке не має сенсу (можливо, "відсутні" / "порожні" - не найкращі терміни). Але значення "відомо" є безглуздим. Якщо сервер існує, але процес виведення значення виходить з ладу, його вимірювання є дійсним, але не дає результату "невідомого" значення.
ексгума

2
@exhuma Я б тоді описав це як "не застосовується".
Вінсент

6
З цікавості, яке вимірювання ви проводите там, де "порожній" не просто дорівнює нулю будь-якої шкали? "Невідомий" / "відсутній" Я можу бачити, що він корисний, наприклад, якщо датчик не підключений або якщо вихідний сигнал датчика з тієї чи іншої причини є сміттям, але "порожній" у кожному випадку, про що я думаю, може бути більш послідовно представлений знаком " 0, []або {}(скаляр 0, порожній список і порожня карта відповідно). Крім того, значення "відсутнє" / "невідоме" є в основному саме тим null, для чого це - воно означає, що там може бути об'єкт, але його немає.
Нік Хартлі

7
Яке б рішення ви не використовували для цього, не забудьте запитати, чи страждає воно від подібних проблем з тими, які змусили вас в першу чергу усунути NULL.
Рей

Відповіді:


85

Загальний спосіб зробити це, принаймні, з функціональними мовами - використовувати дискримінований союз. Тоді це значення, яке є одним із дійсних int, значення, яке позначає "відсутнє", або значення, яке позначає "невідомо". У F # це може виглядати приблизно так:

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

MeasurementЗначення буде тоді Reading, з цілочисельним значенням, або Missing, або Unknownз вихідними даними , як value(якщо це необхідно).

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


7
Ви можете робити типи сум на мовах OO, але є досить небагато плит котла, щоб змусити їх працювати stackoverflow.com/questions/3151702/…
jk.

11
"[На нефункціональних мовах мов] ця модель вам, ймовірно, не принесе великої користі" - Це досить поширена модель в OOP. GOF має варіацію цього шаблону, і такі мови, як C ++, пропонують натурні конструкції для кодування.
Конрад Рудольф

14
@jk. Так, вони не рахуються (ну, мабуть, так і є; вони просто погані в цьому сценарії через відсутність безпеки). Я мав на увазі std::variant(і його духовних попередників).
Конрад Рудольф

2
@Ewan Ні, це говорить "Вимірювання - це тип даних, який є ... або ...".
Конрад Рудольф

2
@DavidArno Добре, навіть без DU, в OOP для цього є "канонічне" рішення, яке має мати надклас значень з підкласами для дійсних та недійсних значень. Але це, мабуть, надто далеко (і на практиці здається, що більшість кодів базує поліморфізм підкласу на користь прапора для цього, як показано в інших відповідях).
Конрад Рудольф

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
@Claude: Ви повинні прочитати мій підручник. Монада - це загальний тип, який дотримується певних правил і забезпечує можливість зв’язувати ланцюжок операцій, тому в цьому випадку Measurement<T>це монадичний тип.
Ерік Ліпперт

5
@daboross: Хоча я згоден з тим, що державні монади - це хороший спосіб запровадити монади, я не думаю, що держава є такою, що характеризує монаду. Я думаю, що переконлива функція може поєднувати послідовність функцій; державність - це лише деталь реалізації.
Ерік Ліпперт

18

Я думаю, що в цьому випадку корисною буде варіація на Null Object Pattern:

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. Вони могли реалізувати або змінити методи у батьківському класі вимірювання. +1
Грег Бургхардт

2
Чи не сенс шаблону Null Object, що ви не працюєте на недійсних значеннях, а скоріше нічого не робите?
Кріс Волетрт

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
}

10
@KakturusOption<Option<Int>>
Берги

5
@Bergi Ви не можете подумати, що це навіть віддалено прийнятно ..
BlueRaja - Danny Pflughoeft

8
@ BlueRaja-DannyPflughoeft Насправді він досить добре відповідає опису ОП, який також має вкладену структуру. Щоб стати прийнятним, ми, звичайно, запровадимо псевдонім належного типу (або "newtype"), але 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завжди повертає помилкові, коли один з них є 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

Якби я переймався питанням "щось зробити", а не елегантним рішенням, швидким і брудним злом було б просто використовувати рядки "невідомо", "відсутні" та "рядкове представлення мого числового значення", яке б потім було конвертується з рядка і використовується за потребою. Реалізується швидше, ніж це писати, і, принаймні, в деяких обставинах, цілком адекватно. (Зараз я формую пул ставок на кількість знижок ...)


Запропонований тим, що згадував "щось робити".
До побачення пані Чіпс

4
Деякі люди можуть зауважити, що це страждає від більшості тих самих проблем, що і використання NULL, а саме, що він просто переходить від необхідності перевірки NULL до необхідності "невідомого" та "відсутнього" чеків, але зберігає збій часу запуску для щасливої, мовчазної корупції даних для нещасливий як єдиний показник, що ви забули чек. Навіть відсутній чек NULL має ту перевагу, що лінери можуть їх спіймати, але це втрачає це. Це додає відмінності між "невідомим" і "зниклим", однак, там він б'є NULL ...
8bittree

2

Суть, якщо питання, здається, "Як я повертаю два непов'язані фрагменти інформації з методу, який повертає єдину цілу? Я ніколи не хочу перевіряти свої повернені значення, а нулі погані, не використовуйте їх".

Давайте подивимось, що ви хочете пройти. Ви передаєте або int, або не-int обґрунтування, чому ви не можете дати Int. Питання стверджує, що буде лише дві причини, але кожен, хто коли-небудь зробив перерахунок, знає, що будь-який список зростатиме. Область застосування інших обґрунтувань просто має сенс.

Тоді спочатку це виглядає так, що це може бути гарним випадком для кидання винятку.

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

І це ТІЛЬКА система, яка дозволяє повернути гарантійно-дійсні вставки та гарантувати, що кожен оператор int та метод, який приймає вставки, можуть прийняти значення повернення цього методу, не маючи необхідності перевіряти наявність недійсних значень, таких як нульові чи магічні значення.

Але винятки - це лише прийнятне рішення, якщо, як випливає з назви, це винятковий випадок, а не звичайний хід бізнесу.

І спроба / улов і обробник - це стільки ж котла, скільки і нульова перевірка, на що в першу чергу заперечували.

І якщо абонент не містить спроби / лову, то абонент, що викликає, повинен викликати і так далі.


Наївний другий прохід - сказати "Це вимірювання. Негативні вимірювання відстані навряд чи". Так що для деякого вимірювання Y, ви можете просто мати витрати на

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

Це робиться так, як у багатьох старих системах C, і навіть у сучасних системах, де існує справжнє обмеження для int, і ви не можете зафіксувати його на структурі чи монаді якогось типу.

Якщо вимірювання можуть бути негативними, ви просто збільшите тип даних (наприклад, довгий int) і магічні значення будуть вищими за діапазон int, і в ідеалі починаєте з деякого значення, яке буде чітко відображатися в налагоджувачі.

Однак є вагомі причини мати їх як окрему змінну, а не просто мати магічні числа. Наприклад, суворий набір тексту, ремонтопридатність та відповідність очікуванням.


Тоді в нашій третій спробі ми розглядаємо випадки, коли нормальним ходом бізнесу є неінтимні значення. Наприклад, якщо колекція цих значень може містити кілька нецілих записів. Це означає, що обробником винятків може бути неправильний підхід.

У такому випадку це виглядає непоганим випадком для структури, яка передає int, та обґрунтування. Знову ж таки, це обґрунтування може бути просто такою, як вище, але замість того, щоб обидва вони трималися в одному і тому ж, ви зберігаєте їх як окремі частини структури. Спочатку у нас є правило, що якщо встановлено обґрунтування, int не буде встановлено. Але ми більше не прив’язані до цього правила; ми можемо надати обґрунтування і для дійсних чисел, якщо буде потреба.

У будь-якому випадку, кожен раз, коли ви його називаєте, вам все-таки потрібна котельна плита, щоб перевірити обґрунтування, щоб перевірити, чи є дійсний int, а потім витягніть та використовуйте частину int, якщо обґрунтування дозволяє нам.

Саме тут вам потрібно дослідити свої міркування щодо "не використовувати нуль".

Як і винятки, "null" означає винятковий стан.

Якщо абонент викликає цей метод і повністю ігнорує "обгрунтування" частини структури, очікуючи число без будь-якої обробки помилок, і він отримує нуль, то він буде обробляти нуль як число і помиляється. Якщо воно отримає магічне число, воно сприйме це як число і помиляється. Але якщо він отримує нуль, він буде падати , так як це , чорт забирай, треба робити.

Тому щоразу, коли ви закликаєте цей метод, ви повинні встановити чеки на його повернене значення, однак ви обробляєте недійсні значення, вхідні чи поза діапазону, спробуйте / ловити, перевіряючи структуру на "обґрунтування" компонента, перевіряючи int для магічного числа або перевірки int на нуль ...

Альтернативно, для обробки множення результату, який може містити недійсний int та обґрунтування типу "Моя собака з'їла це вимірювання", - це перевантажувати оператора множення для цієї структури.

... А потім перевантажуйте кожного іншого оператора у вашій програмі, який може застосувати ці дані.

... А потім перевантажуйте всі методи, які можуть приймати вставки.

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

Тож оригінальна передумова є помилковою різними способами:

  1. Якщо у вас є недійсні значення, ви не можете уникнути перевірки цих недійсних значень у будь-якій точці коду, де ви обробляєте ці значення.
  2. Якщо ви повертаєте щось, окрім int, ви не повертаєте int, тому не можете ставитися до цього як до int. Перевантаження оператора дозволяє прикидатися , але це лише прикидатися.
  3. Інт з магічними числами (включаючи NULL, NAN, Inf ...) більше не є int, це структура бідолахи.
  4. Уникнення нулів не зробить код більш надійним, він просто приховає проблеми з ints або перемістить їх у складну структуру обробки винятків.

1

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

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

В інших мовах є положення про охорону (наприклад, Swift або Ruby), а Ruby має умовне раннє повернення.

Я бачив, як це вирішено в Python кількома різними способами:

  • зі структурою даних для обгортки, оскільки числова інформація зазвичай збирається до сутності та має час вимірювання. Обгортка може змінювати такі магічні методи, __mult__щоб винятки не виникали, коли з'являються ваші невідомі або відсутні значення Numpy і панди можуть мати такі можливості.
  • зі значенням дозорного (наприклад, вашим Unknownабо -1 / -2) та викладом if
  • з окремим булевим прапором
  • зі лінивою структурою даних - ваша функція виконує деяку операцію над структурою, потім вона повертається. Найбільш зовнішня функція, яка потребує фактичного результату, оцінює ліниву структуру даних
  • з ледачим конвеєром операцій - подібним до попереднього, але цей може бути використаний на наборі даних або в базі даних

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придушуються лише тоді, коли ви прямо просите їх придушити. Ще одна перевага полягає в тому, що такий підхід - це самодокументування: кожна функція показує, чи очікує вона невідомого чи відсутнього та як функція.

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

sigmoidВи все ще можете зателефонувати sin, навіть якщо він не очікує Missingабо Unknown, оскільки sigmoidдекоратор вибере виняток.


1
цікаво, в чому сенс опублікувати дві відповіді на те саме запитання (це ваша попередня відповідь , щось не так?)
gnat

@gnat Ця відповідь дає міркування, чому це не слід робити так, як показує автор, і я не хотів переживати труднощів інтегрувати два відповіді з різними ідеями - просто простіше написати два відповіді, які можна прочитати самостійно . Я не розумію, чому ти так дбаєш про чужі нешкідливі міркування.
noɥʇʎԀʎzɐɹƆ

0

Припустимо отримання кількості процесорів на сервері. Якщо сервер вимкнено або його було замінено, цього значення просто не існує. Це буде вимірювання, яке не має сенсу (можливо, "відсутні" / "порожні" - не найкращі терміни). Але значення "відомо" є безглуздим. Якщо сервер існує, але процес виведення значення виходить з ладу, його вимірювання є дійсним, але не дає результату "невідомого" значення.

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

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


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, сповіщаючи слухачів про зміни.

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


0

Так, концепція декількох різних типів НС існує в деяких мовах; тим більше у статистичних, де це є більш значимим (а саме величезна різниця між відсутніми випадковими, відсутніми-цілком-випадковими, відсутніми-не-випадковими ).

  • якщо ми вимірюємо лише довжину віджетів, то не важливо розрізняти "несправність датчика" або "відключення електроенергії" або "збій мережі" (хоча "числовий переповнення" передає інформацію)

  • але, наприклад, пошук даних або опитування, запитуючи респондентів, наприклад, про їх дохід чи ВІЛ-статус, результат "Невідомого" відрізняється від "Відхилити відповідь", і ви можете бачити, що наші попередні припущення щодо того, як зараховувати останнього, будуть прагнутими бути відмінним від колишнього. Так мови, як SAS, підтримують кілька різних типів NA; мова R не має, але користувачам дуже часто доводиться ламати це; НС у різних точках трубопроводу можна використовувати для позначення дуже різних речей.

  • також є випадок, коли у нас є кілька змінних NA для одного запису ("множинна імпутація"). Приклад: якщо я не знаю вік, поштовий індекс, рівень освіти або дохід людини, складніше зараховувати їх дохід.

Щодо того, як ви представляєте різні типи NA в мовах загального призначення, які не підтримують їх, зазвичай люди зловживають такі речі, як плаваюча точка-NaN (вимагає перетворення цілих чисел), перерахунків або дозорних (наприклад, 999 або -1000) для цілих чи категоричні значення. Зазвичай не дуже чітка відповідь, вибачте.


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відсутня.


@Hulk, які інші функціональні мови підтримують відсутні значення? Навіть якщо вони підтримують пропущені значення, я впевнений, що ви не можете заповнити їх статистичними методами лише в одному рядку коду.
ilhan

-1

Чи є причина, що функціональність *оператора замість цього не може бути змінена?

Більшість відповідей містять якесь значення пошуку, але в цьому випадку може бути просто простіше змінити математичний оператор.

Тоді ви зможете мати подібні empty()/ unknown()функціональні можливості у всьому проекті.


4
Це означає, що вам доведеться перевантажувати всіх операторів
труба
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.