Полупредикатная задача
Эта статья в значительной степени или полностью опирается на один источник . ( февраль 2012 г. ) |
В компьютерном программировании возникает проблема полупредикатов , когда подпрограмма, предназначенная для возврата полезного значения, может дать сбой, но для сигнализации об ошибке используется допустимое в противном случае возвращаемое значение . [1] Проблема в том, что вызывающий подпрограмму не может сказать, что в данном случае означает результат.
Пример
[ редактировать ]Операция деления дает действительное число , но завершается неудачей, когда делитель равен нулю . Если бы нам пришлось написать функцию, выполняющую деление, мы могли бы выбрать возврат 0 при этом недопустимом вводе. Однако, если дивиденд равен 0, результат тоже будет 0. Это означает, что не существует числа, которое мы могли бы вернуть, чтобы однозначно сигнализировать о попытке деления на ноль, поскольку все действительные числа находятся в диапазоне деления.
Практические последствия
[ редактировать ]Ранние программисты обрабатывали потенциально исключительные случаи, такие как деление, используя соглашение, требующее, чтобы вызывающая процедура проверяла входные данные перед вызовом функции деления. Это имело две проблемы: во-первых, это сильно затрудняло весь код, выполняющий деление (очень распространенная операция); во-вторых, это нарушило принципы «Не повторяйся» и принципы инкапсуляции , первый из которых предполагает устранение дублированного кода, а второй — хранить код, связанный с данными, в одном месте (в этом примере разделения проверка входных данных выполнялась отдельно). ). Для вычислений, более сложных, чем деление, вызывающей стороне может быть сложно распознать недопустимый ввод; в некоторых случаях определение достоверности входных данных может оказаться столь же дорогостоящим, как и выполнение всех вычислений. Целевую функцию также можно изменить, и тогда она будет ожидать других предварительных условий, чем вызывающая; такая модификация потребовала бы изменений в каждом месте, где вызывалась функция.
Решения
[ редактировать ]Проблема полупредикатов не является универсальной для функций, которые могут выйти из строя.
Использование специального соглашения для интерпретации возвращаемых значений
[ редактировать ]Если диапазон функции не охватывает все пространство, соответствующее типу данных возвращаемого значения функции, можно использовать значение, которое, как известно, невозможно при обычных вычислениях. Например, рассмотрим функцию index
, который принимает строку и подстроку и возвращает целочисленный индекс подстроки в основной строке. Если поиск не удался, функцию можно запрограммировать на возврат −1 (или любого другого отрицательного значения), поскольку это никогда не может означать успешный результат.
Однако у этого решения есть свои проблемы, поскольку оно перегружает естественное значение функции произвольным соглашением:
- Программист должен запомнить конкретные значения ошибок для многих функций, которые, конечно, не могут быть идентичными, если функции имеют разные диапазоны.
- Другая реализация одной и той же функции может использовать другое значение ошибки, что приводит к возможным ошибкам при переходе программистов из одной среды в другую.
- Если сбойная функция желает передать полезную информацию о том, почему она не удалась, одного значения сбоя недостаточно.
- Целое число со знаком уменьшает вдвое возможный диапазон индексов, чтобы иметь возможность хранить бит знака .
- Хотя выбранное значение является недопустимым результатом для этой операции, оно может быть допустимым входным значением для последующих операций. Например, в Python
str.find
возвращает −1, если подстрока не найдена, [2] но -1 является допустимым индексом (отрицательные индексы обычно начинаются с конца [3] ).
Многозначный доход
[ редактировать ]Многие языки позволяют с помощью того или иного механизма возвращать функции несколько значений. Если это доступно, функцию можно перепроектировать так, чтобы она возвращала логическое значение, сигнализирующее об успехе или неудаче, вместе с основным возвращаемым значением. Если возможны несколько режимов ошибок, функция может вместо этого возвращать перечисляемый код возврата (код ошибки) вместе со своим основным возвращаемым значением.
Различные методы возврата нескольких значений включают в себя:
- Возврат кортежа значений. Это обычное явление в языках (таких как Python ), которые имеют встроенный тип данных кортеж и специальный синтаксис для их обработки: в Python
x, y = f()
вызывает функциюf
возвращает пару значений и присваивает элементы пары двум переменным. - Вторичные возвращаемые значения, как в Common Lisp . Все выражения имеют основное значение, но заинтересованным вызывающим объектам могут быть возвращены вторичные значения. Например,
GETHASH
Функция возвращает значение данного ключа в ассоциативной карте или в противном случае значение по умолчанию. Однако он также возвращает вторичное логическое значение, указывающее, было ли найдено значение, что позволяет различать случаи «значение не найдено» и «найденное значение равно значению по умолчанию». Это отличается от возврата кортежа тем, что вторичные возвращаемые значения являются необязательными — если вызывающая сторона не заботится о них, она может полностью их игнорировать, тогда как возвращаемые значения кортежа являются просто синтаксическим сахаром для возврата и распаковки списка, и каждый вызывающий объект всегда должен знать и использовать все возвращаемые предметы. - Языки с вызовом по ссылке или эквивалентами, такими как вызов по адресу с использованием указателей , могут допускать многозначный возврат, назначая некоторые параметры в качестве выходных параметров . В этом случае функция может просто вернуть значение ошибки, а в функцию будет передана переменная, предназначенная для хранения фактического результата. Это аналогично использованию статуса выхода для хранения кода ошибки и потоков для возврата контента.
- Вариант выходных параметров используется в объектно-ориентированных языках , в которых используется вызов путем совместного использования , где изменяемый объект передается функции, а объект модифицируется для возврата значений.
- Языки логического программирования, такие как Пролог, не имеют возвращаемых значений. Вместо этого в качестве выходных параметров используются несвязанные логические переменные, которые необходимо унифицировать со значениями, созданными при вызове предиката.
Глобальная переменная для статуса возврата
[ редактировать ]Подобно аргументу «out», глобальная переменная может хранить информацию о том, какая ошибка произошла (или просто произошла ли ошибка).
Например, если возникает ошибка, о которой сигнализируется (как правило, как указано выше, недопустимым значением, например -1), Unix errno
переменная установлена, чтобы указать, какое значение произошло. Использование глобальной переменной имеет свои обычные недостатки: безопасность потоков становится проблемой (современные операционные системы используют поточно-безопасную версию errno), и если используется только одна глобальная ошибка, ее тип должен быть достаточно широким, чтобы содержать всю интересную информацию обо всех возможных ошибки в системе.
Исключения
[ редактировать ]Исключения — одна из широко используемых схем решения этой проблемы. Состояние ошибки вообще не считается возвращаемым значением функции; нормальный поток управления нарушается, и явная обработка ошибки происходит автоматически. Они являются примером внеполосной сигнализации .
Расширение типа возвращаемого значения
[ редактировать ]Гибридные типы, созданные вручную
[ редактировать ]В C распространенным подходом, когда это возможно, является намеренное использование более широкого типа данных, чем это необходимо для функции. Например, стандартная функция getchar()
определяется типом возвращаемого значения int
и возвращает значение в диапазоне [0, 255] (диапазон unsigned char
) об успехе или ценности EOF
( определяется реализацией , но вне диапазона unsigned char
) в конце ввода или ошибке чтения.
Ссылочные типы, допускающие значение NULL
[ редактировать ]В языках с указателями или ссылками одним из решений является возврат указателя на значение, а не самого значения. Затем этот указатель возврата может быть установлен в значение null, чтобы указать на ошибку. Обычно он подходит для функций, которые все равно возвращают указатель. Это имеет преимущество в производительности по сравнению со стилем обработки исключений ООП. [4] с тем недостатком, что нерадивые программисты могут не проверить возвращаемое значение, что приведет к сбою при использовании недопустимого указателя. Является ли указатель нулевым или нет, это еще один пример проблемы предикатов; null может быть флагом, указывающим на ошибку, или значением указателя, возвращенным успешно. Распространенным шаблоном в среде UNIX является установка отдельной переменной для указания причины ошибки. Примером этого является стандартная библиотека C. fopen()
функция.
Неявно гибридные типы
[ редактировать ]В динамически типизированных языках, таких как PHP и Lisp , обычным подходом является возврат false
, none
, или null
когда вызов функции завершается неудачно. Это работает, возвращая тип, отличный от обычного возвращаемого типа (таким образом расширяя тип). Это динамически типизированный эквивалент возврата нулевого указателя.
Например, числовая функция обычно возвращает число (int или float), и хотя ноль может быть допустимым ответом, false — нет. Аналогично, функция, которая обычно возвращает строку, может иногда возвращать пустую строку в качестве допустимого ответа, но возвращать false в случае ошибки. Этот процесс жонглирования типами требует осторожности при проверке возвращаемого значения: например, в PHP используйте ===
(т. е. равные и одного типа), а не просто ==
(т.е. равны после автоматического преобразования типа). Он работает только тогда, когда исходная функция не предназначена для возврата логического значения и по-прежнему требует, чтобы информация об ошибке передавалась другими способами.
Явно гибридные типы
[ редактировать ]В Haskell и других языках функционального программирования обычно используется тип данных, размер которого настолько велик, насколько это необходимо для выражения любого возможного результата. Например, можно написать функцию деления, возвращающую тип Maybe Real
и getchar
функция, возвращающая Either String Char
. Первый — это тип опции , который имеет только одно значение ошибки, Nothing
. Второй случай — тегированное объединение : результатом является либо некоторая строка с описательным сообщением об ошибке, либо успешно прочитанный символ. Haskell Система вывода типов помогает гарантировать, что вызывающие программы справятся с возможными ошибками. Поскольку условия ошибки становятся явными в типе функции, просмотр ее сигнатуры сразу же подсказывает программисту, как обрабатывать ошибки. Кроме того, тегированные объединения и типы опций образуют монады , если они наделены соответствующими функциями: это можно использовать для поддержания чистоты кода за счет автоматического распространения необработанных ошибок.
Пример
[ редактировать ]Rust имеет алгебраические типы данных и встроенную функцию Result<T, E>
и Option<T>
типы.
fn find(key: String) -> Option<String> {
if key == "hello" {
Some(key)
} else {
None
}
}
C ++. Представлен язык программирования std::optional<T>
в обновлении C++17 .
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;
}
и std::expected<T, E>
в C++23 обновлении
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;
}
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Норвиг, Питер (1992). «Общее решение проблем». Парадигмы программирования искусственного интеллекта: примеры использования обычного LISP . Морган Кауфманн . п. 127. ИСБН 1-55860-191-0 .
- ^ «Встроенные типы» . Документация Python 3.10.4 .
- ^ "Если
i
илиj
отрицательно, индекс относится к концу последовательностиs
:len(s) + i
илиlen(s) + j
заменяется.» Общие операции с последовательностями, примечание (3) . - ^ Почему исключения должны быть исключительными — пример сравнения производительности .