コンピュータプログラミングにおいて、半述語問題は、有用な値を返すことを意図したサブルーチンが失敗する可能性があるにもかかわらず、失敗の通知に有効な戻り値を使用する場合に発生します。[ 1 ]問題は、この場合、サブルーチンの呼び出し元が結果の意味を判断できないことです。
除算演算は実数を返しますが、除数がゼロの場合は失敗します。除算を実行する関数を作成する場合、この無効な入力に対して0を返すようにするかもしれません。しかし、被除数が0の場合、結果も0になります。つまり、すべての実数は除算の範囲内にあるため、ゼロ除算が試みられたことを一意に識別できる数値を返すことはできません。
初期のプログラマーは、除算のような例外的なケースを処理する際に、呼び出し元のルーチンが除算関数を呼び出す前に入力を検証するという慣習を用いていました。しかし、これには2つの問題がありました。1つ目は、除算(非常に一般的な演算)を実行するすべてのコードが著しく複雑になること。2つ目は、重複したコードを排除することを推奨する「 Don't Repeat Yourself」原則と、データに関連するコードを1か所にまとめることを推奨する「カプセル化」原則に違反することです(この除算の例では、入力の検証が別々に行われていました)。除算よりも複雑な計算では、呼び出し元が無効な入力を認識することが困難になる可能性があり、場合によっては、入力の妥当性を判断するのに、計算全体を実行するのと同程度のコストがかかることもありました。また、対象となる関数が変更されると、呼び出し元とは異なる前提条件を期待するようになり、そのような変更には、関数が呼び出されるすべての箇所を変更する必要が生じました。
半述語問題は、失敗する可能性のある関数すべてに共通するものではない。
関数の値域が、関数の戻り値のデータ型に対応する空間全体をカバーしていない場合、通常の計算ではあり得ない値を使用できます。たとえば、文字列と部分文字列を受け取り、メイン文字列内の部分文字列の整数インデックスを返す関数を考えてみましょう。検索が失敗した場合、関数は −1 (またはその他の負の値) を返すようにプログラムできます。これは、成功した結果を示すことは決してないからです。index
しかし、この解決策には問題点がある。それは、関数の本来の意味を恣意的な慣習で覆い隠してしまうからだ。
str.find returns −1 if the substring is not found,[2] but −1 is a valid index (negative indices generally start from the end[3]).Many languages allow, through one mechanism or another, a function to return multiple values. If this is available, the function can be redesigned to return a boolean value signalling success or failure, along with its primary return value. If multiple error modes are possible, the function may instead return an enumerated return code (error code) along with its primary return value.
Various techniques for returning multiple values include:
x, y = f() calls the function f returning a pair of values and assigns the elements of the pair to two variables.GETHASH function returns the value of the given key in an associative map, or a default value otherwise. However, it also returns a secondary boolean indicating whether the value was found, making it possible to distinguish between the "no value was found" and "the value found was equal to default value" cases. This is different from returning a tuple, in that secondary return values are optional – if a caller does not care about them, it may ignore them completely, whereas tuple-valued returns are merely syntactic sugar for returning and unpacking a list, and every caller must always know about and consume all items returned.Similar to an "out" argument, a global variable can store what error occurred (or simply whether an error occurred).
For instance, if an error occurs, and is signalled (generally as above, by an illegal value like −1) the Unix errno variable is set to indicate which value occurred. Using a global has its usual drawbacks: thread safety becomes a concern (modern operating systems use a thread-safe version of errno), and if only one error global is used, its type must be wide enough to contain all interesting information about all possible errors in the system.
Exceptions are one widely used scheme for solving this problem. An error condition is not considered a return value of the function at all; normal control flow is disrupted, and explicit handling of the error takes place automatically. They are an example of out-of-band signalling.
In C, a common approach, when possible, is to use a data type deliberately wider than strictly needed by the function. For example, the standard function getchar() is defined with return type int and returns a value in the range (the range of unsigned char) on success or the value EOF (implementation-defined, but outside the range of unsigned char) on the end of the input or a read error.
In languages with pointers or references, one solution is to return a pointer to a value, rather than the value itself. This return pointer can then be set to null to indicate an error. It is typically suited to functions that return a pointer anyway. This has a performance advantage over the OOP style of exception handling,[4] with the drawback that negligent programmers may not check the return value, resulting in a crash when the invalid pointer is used. Whether a pointer is null or not is another example of the predicate problem; null may be a flag indicating failure or the value of a pointer returned successfully. A common pattern in the UNIX environment is setting a separate variable to indicate the cause of an error. An example of this is the C standard libraryfopen() function.
In dynamically typed languages, such as PHP and Lisp, the usual approach is to return false, none, or null when the function call fails. This works by returning a type different from the normal return type (thus expanding the type). It is a dynamically typed equivalent to returning a null pointer.
For example, a numeric function normally returns a number (int or float), and while zero might be a valid response, false is not. Similarly, a function that normally returns a string might sometimes return the empty string as a valid response, but return false on failure. This process of type-juggling necessitates care in testing the return value: e.g., in PHP, use === (i.e., equal and of same type) rather than just == (i.e., equal, after automatic type conversion). It works only when the original function is not meant to return a boolean value, and still requires that information about the error be conveyed via other means.
Haskellやその他の関数型プログラミング言語では、あらゆる結果を表現するために必要なだけ大きなデータ型を使用するのが一般的です。たとえば、型 を返す除算関数Maybe Realと、getcharを返す関数を記述できますEither String Char。前者はオプション型であり、失敗値は 1 つだけですNothing。後者はタグ付き共用体です。結果は、説明的なエラー メッセージを含む文字列か、正常に読み取られた文字のいずれかになります。Haskell の型推論システムは、呼び出し元が起こりうるエラーに対処できるようにするのに役立ちます。エラー条件は関数型で明示されるため、そのシグネチャを見るだけで、プログラマはエラーの処理方法をすぐに知ることができます。さらに、タグ付き共用体とオプション型は、適切な関数が備わっているとモナドを形成します。これは、未処理のエラー条件を自動的に伝播することで、コードを整理するために使用できます。
Rustには代数的データ型があり、組み込みのstd::result::Result<T, E>[ 5 ]とstd::option::Option<T>型[ 6 ]が付属しています。
fn find ( key : String ) -> Option < String > { if key == "hello" { Some ( key ) } else { None } }iまたはjが負の場合、インデックスはシーケンスの末尾からの相対値ですs。len(s) + iまたはlen(s) + jが代入されます。」共通シーケンス操作注記(3)。