В компьютерном программировании проблема полупредиката возникает, когда подпрограмма , предназначенный для возврата полезного значения, может завершиться ошибкой, но для сигнализации отказа используется в противном случае допустимое возвращаемое значение . Проблема в том, что вызывающий подпрограмму не может сказать, что означает результат в этом случае.
Операция деления дает действительное число, но не выполняется, когда делитель ноль. Если бы мы должны были написать функцию, которая выполняет деление, мы могли бы выбрать возврат 0 для этого недопустимого ввода. Однако, если дивиденд равен 0, результат тоже будет 0. Это означает, что не существует числа, которое мы можем вернуть, чтобы однозначно сигнализировать о попытке деления на ноль, поскольку все действительные числа находятся в диапазоне деления.
Ранние программисты имели дело с потенциально исключительными случаями, как и в случае деления, используя соглашение , которое требовало от вызывающей процедуры проверки правильности входных данных. перед вызовом функции деления. У этого были две проблемы. Во-первых, он сильно загромождает весь код, выполняющий деление (очень распространенная операция). Во-вторых, он нарушает принципы Не повторяйтесь и инкапсуляция, где первый касается устранения дублированного кода, а второй предполагает, что код, связанный с данными, должен содержаться в одном месте ( в этом случае проверка ввода проводилась отдельно). Если мы представим себе более сложное вычисление, чем деление, вызывающему может быть сложно узнать, что считается недопустимым вводом; в некоторых случаях выяснение того, является ли ввод действительным, может быть столь же затратным, как выполнение всего вычисления, также существует возможность изменения целевой функции, а затем ожидание других предварительных условий, чем те, которые проверил вызывающий, такое изменение потребует изменений во всех местах, откуда вызывалась функция.
Проблема полупредиката не универсальна среди функций, которые могут дать сбой.
Если диапазон функции не охватывает весь тип данных, определенный для функции, значение, как известно, невозможно при нормальном вычисление может быть использовано. Например, рассмотрим функцию index
, которая принимает строку и подстроку и возвращает integer индекс подстроки в основной строке. Если поиск завершился неудачно, функция может быть запрограммирована на возврат -32 768 (или любое другое отрицательное значение), поскольку это никогда не может означать успешный результат.
У этого решения есть свои проблемы, поскольку оно перегружает естественный смысл функции произвольным соглашением.
str.find
возвращает -1, если подстрока не найдена, но -1 является допустимым индексом (отрицательные индексы обычно начинаются с конца).Многие языки позволяют с помощью того или иного механизма функция возвращать несколько значений. Если это доступно, функция может быть перепроектирована так, чтобы возвращать логическое значение, сигнализирующее об успехе или неудаче, в дополнение к своему первичному возвращаемому значению. Если возможны несколько режимов ошибки, функция может вместо этого вернуть перечислимый код возврата (код ошибки) в дополнение к своему первичному возвращаемому значению.
Различные методы возврата нескольких значений включают:
GETHASH
возвращает значение данного ключа в ассоциативной карте или значение по умолчанию в противном случае. Однако он также возвращает вторичное логическое значение, указывающее, было ли значение найдено, что позволяет различать случаи «значение не найдено» и «найденное значение равно значению по умолчанию». Это отличается от возврата кортежа, поскольку вторичные возвращаемые значения являются необязательными - если вызывающий не заботится о них, он может полностью их игнорировать, тогда как возвращаемые значения кортежа - это просто синтаксический сахар для возврата и распаковки список, и каждый вызывающий должен всегда знать и использовать все возвращенные элементы.Аналогично аргументу «out», глобальная переменная может хранить, какая ошибка произошла (или просто произошла ли ошибка).
Например, если возникает ошибка и сигнализируется (обычно, как указано выше, с помощью недопустимого значения, например -1), переменная Unix errno устанавливается для указания какое значение произошло. Использование глобального имеет свои обычные недостатки: безопасность потоков становится проблемой (современные операционные системы используют поточно-ориентированную версию errno), и если используется только одна глобальная ошибка, ее тип должен быть достаточно широким, чтобы содержать вся интересная информация обо всех возможных ошибках в системе.
Исключения - одна из широко используемых схем решения этой проблемы. Состояние ошибки вообще не считается возвращаемым значением функции; нормальный поток управления прерывается, и явная обработка ошибки происходит автоматически. Они являются примером внеполосной сигнализации.
В C, общий подход, по возможности, намеренно использовать тип данных шире, чем это строго необходимо функции. Например, стандартная функция getchar ()
определяется с типом возвращаемого значения int
и возвращает беззнаковый символ в случае успеха или значение EOF
(реализация- определено, но вне диапазона [0, 255]) в конце ввода или при ошибке чтения.
В языках с указателями или ссылками одно из решений - вернуть указатель на значение, а не само значение. Затем для этого указателя возврата можно установить значение null, чтобы указать на ошибку. Обычно он подходит для функций, которые в любом случае возвращают указатель, он имеет преимущество в производительности по сравнению со стилем обработки исключений ООП, с недостатком, заключающимся в том, что небрежные программисты могут не проверять возвращаемое значение, что приводит к сбою при недопустимом используется указатель. Распространенным шаблоном в среде UNIX является установка отдельной переменной для указания причины ошибки. Примером этого является функция стандартной библиотеки C fopen () [2].
В языках сценариев, таких как PHP и Lisp, обычно возвращается " false »,« none »или« null »при сбое вызова функции. Это работает путем возврата другого типа к нормальному типу возвращаемого значения (таким образом, расширяя тип). Это эквивалент с динамической типизацией возврата нулевого указателя.
Например, числовая функция обычно возвращает число (int или float), а ноль может быть допустимым ответом; ложь нет. Точно так же функция, которая обычно возвращает строку, может иногда возвращать пустую строку в качестве действительного ответа, но возвращать ложь при ошибке. Этот процесс манипулирования типами требует осторожности при тестировании возвращаемого значения: например, в PHP используйте === [т.е. равны и одного типа], а не просто == [т.е. равно, после автоматического преобразования типов]. Он работает только в том случае, если исходная функция не предназначена для возврата логического значения и по-прежнему требует, чтобы информация об ошибке передавалась другими способами.
В Haskell и других языках функционального программирования обычно используются типы данных, размер которых не превышает его размера. должен выражать любой возможный результат. Например, мы могли бы написать функция деления, которая вернула тип Maybe Real
, и функция getchar
, возвращающая Either String Char
. Первый - это вариант типа, который имеет только одно значение ошибки, Ничего
. Второй случай - это объединение с тегами : результатом является либо некоторая строка с описательным сообщением об ошибке, либо успешно прочитанный символ. Система вывода типа в Haskell помогает гарантировать, что вызывающая сторона имеет дело с возможными ошибками. Поскольку условия ошибки становятся явными в типе функции, взгляд на ее сигнатуру сразу же подсказывает программисту, как обрабатывать ошибки. Кроме того, помеченные объединения и типы параметров образуют монады, когда наделены соответствующими функциями: это может использоваться для поддержания порядка в коде путем автоматического распространения необработанных условий ошибки.
len (s) + i
или len (s) + j
заменяется. "примечание операций общей последовательности (3)