これを「学術的」な質問と考えてください。私は時々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を回避することで、場合によってはコードをより堅牢にすることができます。
これを行う一般的な方法は、少なくとも関数型言語では、識別された共用体を使用することです。これは、有効なintの1つである値、「欠落」を示す値、または「不明」を示す値です。 F#では、次のようになります。
type Measurement =
| Reading of value : int
| Missing
| Unknown of value : RawData
Measurement
値は、int値を持つReading
、またはMissing
、またはUnknown
として生データをvalue
(もし必要なら)。
ただし、差別化された労働組合やそれに相当するものをサポートする言語を使用していない場合、このパターンはあまり役に立ちません。そこで、たとえば、3つのうちのどれが正しいデータを含んでいるかを示す列挙フィールドを持つクラスを使用できます。
モナドが何であるかまだわからないなら、今日は学ぶのに最適な日でしょう。 OOプログラマー向けの穏やかな紹介があります:
https://ericlippert.com/2013/02/21/monads-part-one/
あなたのシナリオは、「たぶんモナド」の小さな拡張で、C#では_Nullable<T>
_、他の言語では_Optional<T>
_としても知られています。
モナドを表すための抽象型があるとしましょう:
_abstract class Measurement<T> { ... }
_
そして3つのサブクラス:
_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);
_
次に、2つの測定値を組み合わせることができます。
_Measurement<double> d = First().Bind(Second);
_
また、First()
が_Empty<int>
_の場合、d
は_Empty<double>
_になります。
重要なステップは、バインド操作を正しく行うことです。よく考えてください。
この場合、Nullオブジェクトパターンのバリエーションが役立つと思います。
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>
はこれらすべてを実装しますが、null
sの異なるタイプを区別できないという唯一の注意点があります。私はJava=の人ではありませんが、JavaのOptionalInt
は似ており、他の言語はOptional
型を表す独自の機能を備えている可能性があると私は理解しています。
文字通り整数を使用しなければならない場合、考えられる解決策は1つだけです。可能な値の一部を「マジックナンバー」として使用します。これは「欠落」および「不明」を意味します
例: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
}
変数が浮動小数点数である場合、IEEE754(ほとんどの最新のプロセッサと言語でサポートされている浮動小数点数の標準)に裏付けがあります。これはあまり知られていない機能ですが、標準では1つではなく ファミリー全体NaN (not-a-number)の値。これは、アプリケーションで定義された任意の意味に使用できます。たとえば、単精度の浮動小数点数では、2 ^ {22}種類の無効な値を区別するために使用できる空きビットが22個あります。
通常、プログラミングインターフェイスはそのうちの1つのみを公開します(Numpyのnan
など)。明示的なビット操作以外の他の方法を生成する組み込みの方法があるかどうかはわかりませんが、それは、いくつかの低レベルルーチンを書くだけの問題です。 (また、それらの1つがNaNである場合、設計により_a == b
_は常にfalseを返すため、それらを区別するために1つ必要です。)
それらを使用することは、無効なデータを通知するために独自の「マジックナンバー」を再発明するよりも優れています。なぜなら、それらは正しく伝播し、無効であることを通知するからです。たとえば、average()
関数を使用して、特別な値を確認することを忘れます。
唯一のリスクは、ライブラリがそれらを正しくサポートしていないことです。それらは非常にあいまいな機能であるためです。たとえば、シリアル化ライブラリはそれらをすべて同じnan
に「フラット化」する可能性があります(ほとんどの目的でそれと同等に見えます)。
David Arno's answer に続いて、OOPで、そしてScalaによって提供されるようなオブジェクト機能スタイルで、Java 8関数型、またはJava FP Vavr または Fugue などのライブラリは、次のようなものを書きます:
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
(成功値または失敗値のいずれかを含むオブジェクト)もここで使用できます。
問題の理想的な解決策は、既知の障害と既知の信頼性の低い測定値の違いを気にする理由、およびサポートするダウンストリームプロセスにかかっています。この場合の「下流プロセス」は、人間のオペレーターや他の開発者を除外するものではないことに注意してください。
Nullの「第2のフレーバー」を思いついただけでは、下流のプロセスのセットは、適切な動作のセットを導出するための十分な情報を提供しません。
代わりに、ダウンストリームコードによって行われる悪い動作の原因についてのコンテキストの仮定に依存している場合、私はその悪いアーキテクチャと呼びます。
障害の理由と既知の理由のない障害を区別するのに十分な知識があり、その情報が将来の動作を通知する場合は、その知識を下流に伝達するか、インラインで処理する必要があります。
これを処理するためのいくつかのパターン:
null
を使用するだけでは、実際には何の価値もないことに気付きます。エレガントなソリューションではなく「何かを行う」ことに関心がある場合、迅速かつダーティなハックは、文字列「不明」、「欠落」、および「数値の文字列表現」を使用することです。文字列から変換され、必要に応じて使用されます。これを書くよりも早く実装され、少なくともいくつかの状況では、完全に適切です。 (私は今、反対票の数について賭けプールを形成しています...)
質問が「1つのintを返すメソッドから2つの無関係な情報を返すにはどうすればよいですか?戻り値をチェックしたくないのですが、nullは悪いので、使用しないでください」のようです。
あなたが渡したいものを見てみましょう。 intを渡せない理由として、intまたはnon-intrationaleのいずれかを渡しています。この質問では、理由は2つしかないと主張していますが、列挙型を作成したことがある人なら誰でも、リストが増えることを知っています。他の理論的根拠を指定するスコープは、理にかなっています。
最初は、これは例外をスローするのに適したケースのようです。
戻り値の型にない特別なことを発信者に伝えたい場合、例外は適切なシステムであることがよくあります。例外はエラー状態だけではなく、多くのコンテキストと根拠を返して理由を説明することができます今日はintではありません。
そして、これは保証された有効なintを返すことができる唯一のシステムであり、intを取るすべてのint演算子とメソッドが、nullなどの無効な値やマジック値をチェックする必要なく、このメソッドの戻り値を受け入れることができることを保証します。
ただし、例外は、名前が示すように、これが例外的なケースであり、通常のビジネスコースではない場合にのみ、有効なソリューションです。
そして、try/catchおよびハンドラーは、そもそも反対されていたnullチェックと同じくらい定型的です。
そして、呼び出し元にtry/catchが含まれていない場合、呼び出し元の呼び出し元はそれを行う必要があります。
ナイーブセカンドパスとは、「これは測定値です。負の距離測定値はありそうもない」です。したがって、一部の測定値Yでは、次の定数を使用できます。
これは、多くの古いCシステムで行われている方法であり、intへの純粋な制約があり、あるタイプの構造体またはモナドにラップすることができない現代のシステムでも行われます。
測定値が負の値になる可能性がある場合は、データ型を大きく(たとえばlong int)して、マジック値をintの範囲よりも大きくし、理想的にはデバッガーで明確に表示されるいくつかの値で開始します。
ただし、マジックナンバーだけではなく、別の変数としてそれらを使用するのには十分な理由があります。たとえば、厳密な型指定、保守性、期待への準拠などです。
3番目の試みでは、int以外の値を持つことが通常のビジネスコースである場合を検討します。たとえば、これらの値のコレクションに複数の非整数エントリが含まれている場合があります。これは、例外ハンドラが間違ったアプローチである可能性があることを意味します。
その場合、それはintを渡す構造体とその理論的根拠の良いケースに見えます。繰り返しますが、この根拠は上記のようなconstにすぎませんが、両方を同じintに保持するのではなく、構造体の別個の部分として格納します。最初は、根拠が設定されている場合、intは設定されないという規則があります。しかし、私たちはもはやこのルールに縛られていません。必要に応じて、有効な数値の根拠も提供できます。
どちらの方法でも、呼び出すたびに、定型句をテストしてintが有効かどうかを確認するためのボイラープレートが必要です。次に、その論理式で許可されている場合は、int部分を引き出して使用します。
これは、「nullを使用しない」の背後にある理由を調査する必要がある場所です。
例外と同様に、nullは例外的な状態を意味します。
呼び出し元がこのメソッドを呼び出し、構造の「根拠」部分を完全に無視し、エラー処理なしの数値を期待し、ゼロを取得すると、ゼロは数値として処理され、誤りになります。マジックナンバーを取得すると、それは数字として扱われ、間違ったものになります。しかし、それがnullを取得した場合、フォールオーバーします。
したがって、このメソッドを呼び出すたびに、その戻り値をチェックする必要がありますが、インバンドかアウトバンドかを問わず、無効な値を処理し、try/catch、「根拠」コンポーネントの構造体をチェックし、intをチェックしますマジックナンバー、またはnullのintをチェックしています...
無効なintと「私の犬はこの測定値を食べた」のような根拠を含む可能性のある出力の乗算を処理する別の方法は、その構造の乗算演算子をオーバーロードすることです。
...そして、このデータに適用される可能性のある、アプリケーション内の他のすべての演算子をオーバーロードします。
...そして、intを取る可能性のあるすべてのメソッドをオーバーロードします。
...そしてallこれらのオーバーロードのstillには、無効なintのチェックを含める必要があります。この1つのメソッドの戻り値の型は、呼び出した時点では常に有効なintであるかのように返されます。
したがって、元の前提はさまざまな点で間違っています。
あなたの質問の前提がわかりませんが、これが額面の答えです。欠落または空の場合、 math.nan
(Not a Number)を実行できます。 math.nan
に対して任意の数学演算を実行でき、math.nan
のままになります。
不明な値にはNone
(Pythonのnull)を使用できます。とにかく、未知の値を操作するべきではありません。一部の言語(Pythonはそれらの1つではありません)には特別なnull演算子があり、値がnull以外の場合にのみ操作が実行されます。それ以外の場合、値はnullのままです。
他の言語にはガード句(SwiftまたはRubyなど)があり、Rubyには条件付きの早期復帰があります。
私はこれをいくつかの方法でPython=で解決したのを見ました:
__mult__
のようなマジックメソッドをオーバーライドできるため、不明な値や欠落した値が発生しても例外は発生しません。 Numpyとpandasには、そのような機能があるかもしれません。Unknown
または-1/-2など)とifステートメント値がメモリに格納される方法は、言語と実装の詳細によって異なります。プログラマーにとってオブジェクトがどのように振る舞うべきかということです。 (これは私が質問を読む方法です、私が間違っているかどうか教えてください。)
あなたはすでにあなたの質問でその答えを提案しました:数学演算を受け入れ、例外を発生させることなく自分自身を返す独自のクラスを使用してください。 nullチェックを回避したいので、これが必要だと言います。
解決策1:nullチェックを回避しないでください
Missing
は_math.nan
_として表すことができますUnknown
はNone
として表すことができます
複数の値がある場合、filter()
を使用して、Unknown
またはMissing
以外の値、または無視したい値にのみ操作を適用できます関数。
単一のスカラーに作用する関数でnullチェックを必要とするシナリオは想像できません。その場合は、nullチェックを強制することをお勧めします。
解決策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を取得することを期待していない関数を呼び出すと、関数はすぐに発生し、エラーが発生してコールチェーンに伝達されず、エラーが発生した場所を正確に示します。同じことは不明です。
sigmoid
のデコレータが例外をキャッチするため、sin
またはMissing
を予期していなくても、Unknown
はsigmoid
を呼び出すことができます。 。
サーバーのCPUの数をフェッチするとします。サーバーの電源がオフになっているか、サーバーが廃棄されている場合、その値は存在しません。これは意味のない測定値になります(「欠落」/「空」は最良の用語ではない可能性があります)。しかし、その値は無意味であることが「わかっている」。サーバーは存在するが、値をフェッチするプロセスがクラッシュし、それを測定することは有効ですが、失敗して「不明な」値になります。
これらはどちらもエラー状態のように聞こえるので、ここでの最良のオプションは、get_measurement()
にこれらの両方を例外としてすぐにスローすることです(DataSourceUnavailableException
やSpectacularFailureToGetDataException
など)。 、それぞれ)。次に、これらの問題のいずれかが発生した場合、データ収集コードは(後者の場合は再試行するなどして)すぐにそれに反応し、get_measurement()
はint
を返すだけで済みますデータソースからデータを正常に取得できる場合-int
が有効であることがわかります。
状況が例外をサポートしていないか、それらをあまり活用できない場合は、おそらく別の出力からget_measurement()
に返されるエラーコードを使用することをお勧めします。これはCの慣用的なパターンで、実際の出力は入力ポインターに格納され、エラーコードが戻り値として返されます。
Rには組み込みの欠損値サポートがあります。 https://medium.com/coinmonks/dealing-with-missing-data-using-r-3ae428da2d17
編集:私は反対票を投じられたので、少し説明します。
統計を扱うつもりなら、Rは統計学者のために統計学者によって書かれているので、Rなどの統計言語を使用することをお勧めします。欠損値は非常に大きなトピックであり、学期全体を教えてくれます。そして、欠損値についてのみ大きな本があります。
ただし、ドットや「欠落」など、欠落しているデータにマークを付けることもできます。 Rでは、次のことができます define 不足していることの意味。それらを変換する必要はありません。
欠損値を定義する通常の方法は、それらをNA
としてマークすることです。
x <- c(1, 2, NA, 4, "")
次に、欠落している値を確認できます。
is.na(x)
そして、結果は次のようになります。
FALSE FALSE TRUE FALSE FALSE
ご覧のように ""
が欠落していない。脅すことができます""
は不明です。そしてNA
is行方不明です。
与えられた答えは問題ありませんが、それでも空の値と未知の値の間の階層関係を反映していません。
醜い(その抽象化に失敗しているため)が、完全に動作している(Javaの場合):
Optional<Optional<Integer>> unknowableValue;
unknowableValue.ifPresent(emptiableValue -> ...);
Optional<Integer> emptiableValue = unknowableValue.orElse(Optional.empty());
emptiableValue.ifPresent(value -> ...);
int value = emptiableValue.orElse(0);
ここでは、Nice型システムの関数型言語の方が優れています。
実際:empty/missingおよびnknown * non -値は、一部のプロセス状態、一部の生産パイプラインの一部のように見えます。 Excelのように、他のセルを参照する数式を含むスプレッドシートのセル。そこで、コンテキストラムダを格納することを考えるかもしれません。セルを変更すると、再帰的に依存するすべてのセルが再評価されます。
その場合、int値はintサプライヤーによって取得されます。空の値は、intサプライヤーに空の例外をスローするか、空と評価されます(再帰的に上向き)。あなたの主な数式はすべての値を結びつけ、おそらく空(値/例外)も返します。不明な値は、例外をスローして評価を無効にします。
Javaバインドされたプロパティのように、値は監視可能であり、変更をリスナーに通知します。
要するに:空で不明な追加の状態を持つ値が必要となる繰り返しのパターンは、バインドされたプロパティデータモデルのようなよりスプレッドシートが良いことを示しているようです
はい、一部の言語には複数の異なるNAタイプの概念が存在します。より意味のある統計的なものではもっとそうです(つまり、 Missing-At-Random、Missing-Completely-At-Random、Missing-Not-At-ランダム )。
ウィジェットの長さのみを測定している場合、「センサー障害」、「電源切断」、「ネットワーク障害」を区別することは重要ではありません(「数値オーバーフロー」は情報を伝えます)
しかし、例えばデータマイニングまたは調査。たとえば、回答者に彼らの収入やHIVの状態、「不明」の結果は「回答を辞退」とは異なり、後者を推定する方法についての以前の仮定は前者とは異なる傾向があることがわかります。したがって、SASのような言語は複数の異なるNAタイプをサポートします。R言語はサポートしませんが、ユーザーはそれをハックする必要が頻繁にあります。パイプラインの異なるポイントのNAを使用して、非常に異なるものを表すことができます。 。
それらをサポートしない汎用言語でさまざまなNAタイプをどのように表現するかについては、一般的に人々は浮動小数点NaN(整数の変換が必要)、列挙型またはセンチネル(例:999または-1000)などの整数またはカテゴリー値。通常、あまり明確な答えはありません。申し訳ありません。