Semipredicate problem
In computer programming, a semipredicate problem occurs when a subroutine intended to return a useful value can fail, but the signalling of failure uses an otherwise valid return value.[1] The problem is that the caller of the subroutine cannot tell what the result means in this case. ExampleThe division operation yields a real number, but fails when the divisor is zero. If we were to write a function that performs division, we might choose to return 0 on this invalid input. However, if the dividend is 0, the result is 0 too. This means that there is no number we can return to uniquely signal attempted division by zero, since all real numbers are in the range of division. Practical implicationsEarly programmers handled potentially exceptional cases such as division using a convention requiring the calling routine to verify the inputs before calling the division function. This had two problems: first, it greatly encumbered all code that performed division (a very common operation); second, it violated the Don't repeat yourself and encapsulation principles, the former of which suggesting eliminating duplicated code, and the latter suggesting that data-associated code be contained in one place (in this division example, the verification of input was done separately). For a computation more complicated than division, it could be difficult for the caller to recognize invalid input; in some cases, determining input validity may be as costly as performing the entire computation. The target function could also be modified and would then expect different preconditions than would the caller; such a modification would require changes in every place where the function was called. SolutionsThe semipredicate problem is not universal among functions that can fail. Using a custom convention to interpret return valuesIf the range of a function does not cover the entire space corresponding to the data type of the function's return value, a value known to be impossible under normal computation can be used. For example, consider the function This solution has its problems, though, as it overloads the natural meaning of a function with an arbitrary convention:
Multivalued returnMany 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:
Global variable for return statusSimilar 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 ExceptionsExceptions 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. Expanding the return value typeManually created hybrid typesIn 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 Nullable reference typesIn 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 library Implicitly hybrid typesIn dynamically typed languages, such as PHP and Lisp, the usual approach is to return 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 Explicitly hybrid typesIn Haskell and other functional programming languages, it is common to use a data type that is just as big as it needs to be to express any possible result. For example, one can write a division function that returned the type ExampleRust has algebraic data types and comes with the built-in fn find(key: String) -> Option<String> {
if key == "hello" {
Some(key)
} else {
None
}
}
The C++ programming language introduced std::optional<int> find_int_in_str(std::string_view str) {
constexpr auto digits = "0123456789";
auto n = str.find_first_of(digits);
if (n == std::string::npos) {
// The string simply contains no numbers, not necessarily an error
return std::nullopt;
}
int result;
// More search logic that sets 'result'
return result;
}
and enum class parse_error {
kEmptyString,
kOutOfRange,
kNotANumber
};
std::expected<int, parse_error> parse_number(std::string_view str) {
if (str.empty()) {
// Flag one unexpected situation out of several
return std::unexpected(parse_error::kEmptyString);
}
int result;
// More conversion logic that sets 'result'
return result;
}
See alsoReferences
|
Portal di Ensiklopedia Dunia