Полупредикатная проблема - Semipredicate problem

В компьютерное программирование, а проблема полупредиката происходит, когда подпрограмма предназначенный для возврата полезного значения, может дать сбой, но для сигнализации об ошибке используется действительный возвращаемое значение.[1] Проблема в том, что вызывающий подпрограмму не может сказать, что означает результат в этом случае.

Пример

В разделение операция дает настоящий номер, но не работает, когда делитель нуль. Если бы мы должны были написать функцию, которая выполняет деление, мы могли бы выбрать возврат 0 для этого недопустимого ввода. Однако, если дивиденд равен 0, результат тоже будет 0. Это означает, что нет числа, которое мы можем вернуть, чтобы однозначно сигнализировать о попытке деления на ноль, поскольку все действительные числа находятся в классифицировать деления.

Практические последствия

Ранние программисты имели дело с потенциально исключительными случаями, например, в случае деления, используя соглашение это требовало, чтобы вызывающая подпрограмма проверяла допустимость входных данных перед вызовом функции деления. У этого были две проблемы. Во-первых, он сильно загромождает весь код, выполняющий деление (очень распространенная операция). Во-вторых, это нарушает Не повторяйся и инкапсуляция Принципы, где первый касается исключения дублированного кода, а второй предполагает, что код, связанный с данными, должен содержаться в одном месте (в этом случае проверка ввода выполнялась отдельно). Если мы представим себе более сложное вычисление, чем деление, вызывающему может быть трудно узнать, что считается недопустимым вводом; в некоторых случаях выяснение того, является ли ввод действительным, может быть столь же дорогостоящим, как выполнение всего вычисления, также существует возможность изменения целевой функции, а затем ожидание других предварительных условий, чем те, которые проверил вызывающий, такое изменение потребует изменений во всех местах, откуда вызывалась функция.

Решения

Проблема полупредиката не универсальна среди функций, которые могут дать сбой.

Использование пользовательского соглашения для интерпретации возвращаемых значений

Если диапазон функции не покрывает весь тип данных определенная для функции, можно использовать значение, которое невозможно при обычном вычислении. Например, рассмотрим функцию индекс, который принимает строку и подстроку и возвращает целое число индекс подстроки в основной строке. Если поиск не удался, функция может быть запрограммирована на возвращение -32 768 (или любое другое отрицательное значение), поскольку это никогда не может означать успешный результат.

Однако это решение имеет свои проблемы, поскольку оно перегружает естественный смысл функции произвольным соглашением.

  • программист должен помнить конкретные значения отказов для многих функций, которые, конечно, не могут быть идентичными, если функции имеют разные домены.
  • отличающийся выполнение одной и той же функции может выбрать использование другого значения отказа, что приведет к ошибки когда программисты переходят из среды в среду.
  • если сбойная функция хочет сообщить полезную информацию о том, почему она потерпела неудачу, одного значения сбоя недостаточно.
  • целое число со знаком вдвое уменьшает возможный диапазон индексов, чтобы можно было сохранить знаковый бит.
  • в то время как дозорный является «недопустимым результатом» для этой операции, он может быть действительным вводом для последующих операций, например в Python ул. найти возвращает -1, если подстрока не найдена[2], но -1 - допустимый индекс (отрицательные индексы обычно начинаются с конца[3]).

Многозначная отдача

Многие языки позволяют функции с помощью того или иного механизма возвращать несколько значений. Если это доступно, функция может быть перепроектирована так, чтобы возвращать логическое значение, сигнализирующее об успехе или неудаче, в дополнение к своему первичному возвращаемому значению. Если возможны несколько режимов ошибки, функция может вместо этого вернуть перечислимый код возврата (код ошибки) в дополнение к его первичному возвращаемому значению.

К различным методам возврата нескольких значений относятся:

  • Возвращение кортеж ценностей. Это принято в таких языках, как Python, которые имеют встроенный тип данных кортеж и специальный синтаксис для их обработки: в Python, х, у = f () вызывает функцию ж который возвращает пару значений и присваивает элементы пары двум переменным.
  • Вторичные возвращаемые значения, как в Common Lisp. Все выражения имеют первичное значение, но вторичные значения могут быть возвращены заинтересованным вызывающим абонентам. Например, GETHASH функция возвращает значение данного ключа в ассоциативная карта или значение по умолчанию в противном случае. Однако он также возвращает вторичное логическое значение, указывающее, было ли найдено значение, что позволяет различать случаи «значение не найдено» и «найденное значение равно значению по умолчанию». Это отличается от возврата кортежа, поскольку вторичные возвращаемые значения необязательный - если вызывающий не заботится о них, он может полностью их игнорировать, тогда как возвращаемые значения кортежа просто синтаксический сахар для возврата и распаковки списка, и каждый вызывающий должен всегда знать и использовать все возвращенные предметы.
  • Языки с позвонить по ссылке - или эквиваленты, такие как позвонить по адресу с помощью указатели - может обеспечить многозначную отдачу, указав некоторые параметры как выходные параметры. В этом случае функция могла бы просто вернуть значение ошибки с переменной, предназначенной для хранения фактического результата, передаваемого в функцию. Это аналогично использованию статус выхода хранить код ошибки, и потоки для возврата контента.
  • Вариант выходных параметров используется в объектно-ориентированные языки это использование позвонить, поделившись, где изменяемый объект передается в функцию, а объект видоизменяется для возврата значений.
  • Логическое программирование языки, такие как Пролог даже нет возвращаемых значений. Вместо этого несвязанные логические переменные используются в качестве выходных параметров, чтобы единый со значениями, созданными в вызове предиката.

Глобальная переменная для статуса возврата

Подобно аргументу «вне», глобальная переменная может запоминать, какая ошибка произошла (или просто была ли ошибка).

Например, если возникает ошибка и сигнализируется (обычно, как указано выше, с помощью недопустимого значения, например -1), Unix errno переменная установлена, чтобы указать, какое значение произошло. Использование глобального имеет свои обычные недостатки: безопасность потоков становится проблемой (современные операционные системы используют поточно-ориентированную версию errno), и если используется только одна глобальная ошибка, ее тип должен быть достаточно широким, чтобы содержать всю интересную информацию обо всех возможных ошибках в системе.

Исключения

Исключения являются одной из широко используемых схем решения этой проблемы. Состояние ошибки вообще не считается возвращаемым значением функции; нормальный поток управления прерывается, и явная обработка ошибки происходит автоматически. Они являются примером внеполосная сигнализация.

Расширение типа возвращаемого значения

Созданные вручную гибридные типы

В C, общий подход, когда это возможно, состоит в том, чтобы использовать тип данных, умышленно более широкий, чем это строго необходимо для функции. Например, стандартная функция getchar () определяется с возвращаемым типом int и возвращает беззнаковый символ в случае успеха или значение EOF (определяется реализацией, но вне диапазона [0, 255]) в конце ввода или при ошибке чтения.

Обнуляемые ссылочные типы

В языках с указателями или ссылками одним из решений является возврат указателя на значение, а не на само значение. Затем этот указатель возврата может быть установлен на ноль чтобы указать на ошибку. Обычно он подходит для функций, которые в любом случае возвращают указатель, имеет преимущество в производительности по сравнению со стилем обработки исключений ООП.[4], с тем недостатком, что нерадивые программисты могут не проверить возвращаемое значение, что приведет к крушение когда используется недопустимый указатель. Обычный образец в UNIX среда устанавливает отдельную Переменная чтобы указать причину ошибки. Примером этого является Стандартная библиотека C fopen ()[2] функция.

Неявно гибридные типы

В языки сценариев, Такие как PHP и Лисп, обычный подход состоит в том, чтобы вернуть «false», «none» или «null» при сбое вызова функции. Это работает путем возврата другого типа к нормальному типу возвращаемого значения (таким образом, расширяя тип). Это эквивалент с динамической типизацией возврата нулевого указателя.

Например, числовая функция обычно возвращает число (int или float), а ноль может быть допустимым ответом; ложь нет. Точно так же функция, которая обычно возвращает строку, может иногда возвращать пустую строку в качестве допустимого ответа, но возвращать ложь при ошибке. Этот процесс манипулирования типами требует осторожности при тестировании возвращаемого значения: например, в PHP используйте === [т.е. равные и одного типа], а не просто == [т.е. равно, после автоматического преобразования типов]. Он работает только в том случае, если исходная функция не предназначена для возврата логического значения и по-прежнему требует, чтобы информация об ошибке передавалась другими способами.

Явно гибридные типы

В Haskell и другие функциональные языки программирования, обычно используется тип данных такого размера, который необходим для выражения любого возможного результата. Например, мы могли бы написать функцию деления, которая возвращала бы тип Может быть реально, а Getchar функция возврат Либо String Char. Первый - это тип опциона, который имеет только одно значение отказа, Ничего. Второй случай - это помеченный союз: результат - это либо строка с описательным сообщением об ошибке, либо успешно прочитанный символ. Haskell's вывод типа Система помогает гарантировать, что вызывающие абоненты устранят возможные ошибки. Поскольку условия ошибки становятся явными в типе функции, просмотр ее сигнатуры сразу же подсказывает программисту, как обрабатывать ошибки. Кроме того, помеченные союзы и типы опционов образуют монады при наделении соответствующими функциями: это может использоваться для поддержания порядка в коде путем автоматического распространения необработанных ошибок.

Смотрите также

Рекомендации

  1. ^ Норвиг, Питер (1992), "Решение общих проблем", Парадигмы программирования искусственного интеллекта: общие примеры LISP (3-е изд.), Морган Кауфманн, п. 127; 946, г. ISBN  1-55860-191-0
  2. ^ [1]
  3. ^ "Если i или j отрицательны, индекс отсчитывается относительно конца последовательности s: len (s) + я или же len (s) + j заменяется ".примечание об общих последовательностях операций (3)
  4. ^ Почему исключения должны быть исключительными - пример сравнения производительности