Спецификация пространства имен в отсутствие неопределенности - PullRequest
1 голос
/ 12 февраля 2009

Почему некоторые языки, такие как C ++ и Python, требуют указания пространства имен объекта, даже если не существует двусмысленности? Я понимаю, что в этом есть бэкдоры, такие как using namespace x в C ++ или from x import * в Python. Однако я не могу понять причину нежелания языка просто «делать правильные вещи», когда только одно доступное пространство имен содержит заданный идентификатор и не существует двусмысленности. Для меня это просто ненужное многословие и нарушение DRY, так как вы вынуждены указать что-то, что компилятор уже знает.

Например:

import foo  # Contains someFunction().

someFunction()  # imported from foo.  No ambiguity.  Works.

Vs.

import foo  # Contains someFunction()
import bar  # Contains someFunction() also.

# foo.someFunction or bar.someFunction?  Should be an error only because
# ambiguity exists.
someFunction() 

Ответы [ 8 ]

11 голосов
/ 12 февраля 2009

Python считает, что «явное лучше, чем неявное». (введите import this в интерпретатор Python)

Кроме того, скажите, что я читаю чей-то код. Возможно, это ваш код; возможно это мой код от шести месяцев назад. Я вижу ссылку на bar(). Откуда взялась эта функция? Я мог бы просмотреть файл на def bar(), но если я его не найду, что тогда? Если python автоматически находит первый bar (), доступный через импорт, то мне нужно найти каждый импортированный файл, чтобы найти его. Какая боль! А что если поиск функций повторяется через иерархию импорта?

Я бы лучше посмотрел zomg.bar(); это говорит мне, откуда эта функция, и гарантирует, что я всегда получаю один и тот же, если код изменяется (если я не изменяю модуль zomg).

11 голосов
/ 12 февраля 2009

Одной из причин является защита от случайного возникновения конфликта при последующем изменении кода (или для внешнего модуля / библиотеки, когда его изменяет кто-то другой). Например, в Python вы можете написать

from foo import *
from bar import *

без конфликтов, если вы знаете, что модули foo и bar не имеют переменных с одинаковыми именами. Но что, если в более поздних версиях foo и bar содержат переменные с именем rofl? Тогда bar.rofl покроет foo.rofl без вашего ведома.

Мне также нравится иметь возможность заглянуть в начало файла и точно узнать, какие имена импортируются и откуда они берутся (я говорю о Python, конечно, но могут применяться те же соображения для C ++).

5 голосов
/ 12 февраля 2009

Проблема в абстракции и повторном использовании: вы на самом деле не знаете, будет ли в будущем двусмысленность .

Например, очень часто в проекте настраиваются разные библиотеки, чтобы обнаружить, что у них у всех есть свои собственные реализации класса строк, называемые «строка». Затем ваш компилятор будет жаловаться на неоднозначность, если библиотеки не инкапсулированы в отдельных пространствах имен.

Это восхитительное удовольствие, чтобы избежать такой неоднозначности, указав, какую реализацию (например, стандартную std :: string one) вы хотите использовать в каждой конкретной инструкции или контексте (читай: scope).

И если вы думаете, что это очевидно в определенном контексте (читай: в конкретной функции или .cpp в c ++, .py file в python - НИКОГДА в заголовочных файлах C ++) вам просто нужно выразить себя и сказать, что «это должно быть очевидно», добавив инструкцию «using namespace» (или import *) . Пока компилятор не будет жаловаться, потому что это не так.

Если вы используете использование в определенных областях, вы не нарушаете правило СУХОЙ.

4 голосов
/ 12 февраля 2009

Были языки, в которых компилятор пытался «делать правильные вещи» - на ум приходят Algol и PL / I. Причина, по которой их больше нет, заключается в том, что компиляторы очень плохо делают правильные вещи, но очень хорошо делают неправильные, учитывая половину шансов!

1 голос
/ 12 февраля 2009

Это действительно правильно?

Что делать, если у меня есть два типа :: bat и :: foo :: bar

Я хочу сослаться на тип летучей мыши, но случайно нажал клавишу r вместо t (они рядом друг с другом).

Правильно ли для компилятора выполнять поиск в каждом пространстве имен, чтобы найти :: foo :: bar, даже не предупредив меня?

Или что делать, если я использую "bar" как сокращение для типа ":: foo :: bar" по всей моей кодовой базе. Затем однажды я включаю библиотеку, которая определяет тип данных :: bar. Внезапно возникает неопределенность там, где ее раньше не было. И вдруг «правильная вещь» стала неправильной.

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

В качестве альтернативы, это может привести к ошибке, но это будет просто смешно, потому что даже при существующих правилах языка здесь не должно быть двусмысленности, поскольку один из типов скрыт в пространстве имен I не указывать, поэтому это не следует рассматривать.

Другая проблема заключается в том, что компилятор может не знать, какие существуют другие типы. В C ++ порядок определений имеет значение.

В C # типы можно определять в отдельных сборках и ссылаться на них в вашем коде. Как компилятор узнает, что другого типа с таким же именем не существует в другой сборке, просто в другом пространстве имен? Как он узнает, что один не будет добавлен к другой сборке позже?

Правильнее всего делать то, что доставляет программисту меньше всего неприятных сюрпризов. Второе предположение программиста на основе неполных данных, как правило, не является правильным решением.

В большинстве языков есть несколько инструментов, позволяющих избежать указания пространства имен.

В c ++ у вас есть "использование пространства имен foo", а также typedefs. Если вы не хотите повторять префикс пространства имен, не делайте этого. Используйте инструменты, предоставляемые языком, поэтому вам не нужно.

1 голос
/ 12 февраля 2009

Идеал, к которому стремится это правило, состоит в том, чтобы упростить создание повторно используемых компонентов - и если вы повторно используете свой компонент, вы просто не знаете, какие символы будут определены в других пространствах имен, используемых клиентом. Таким образом, правило заставляет вас четко заявить о своем намерении в отношении дальнейших определений, о которых вы еще не знаете.

Однако этот идеал не был достигнут для C ++, в основном из-за поиска Кенига.

0 голосов
/ 12 февраля 2009

Интересный вопрос. В случае C ++, на мой взгляд, при условии, что компилятор пометил ошибку, как только возник конфликт, единственная проблема, которую это может вызвать, была бы:

Автоматический поиск всех пространств имен C ++ исключил бы возможность скрывать имена внутренних частей кода библиотеки.

Код библиотеки часто содержит части (типы, функции, глобальные переменные), которые никогда не предназначены для того, чтобы быть видимыми «внешнему миру». C ++ имеет безымянные пространства имен именно по этой причине - чтобы избежать «засорения внутренних частей» глобального пространства имен, даже когда эти пространства имен библиотеки явно импортированы с using namespace xyz;.

Пример. Предположим, что C ++ сделал автоматически выполняет поиск, а конкретная реализация Стандартной библиотеки C ++ содержала внутреннюю вспомогательную функцию std::helper_func(). Предположим, что пользователь Джо разрабатывает приложение, содержащее функцию joe::helper_func() с использованием другой реализации библиотеки , которая не содержит std::helper_func(), и вызывает свой собственный метод с использованием неквалифицированных вызовов для helper_func(). Теперь код Джо прекрасно скомпилируется в его среде, но любой другой пользователь, который пытается скомпилировать этот код с использованием первой реализации библиотеки, будет получать сообщения об ошибках компилятора. Поэтому первое, что требуется для переносимости кода Джо, - это вставить соответствующие объявления / директивы using или использовать полностью определенные идентификаторы. Другими словами, автоматический поиск ничего не покупает для переносимого кода.

Правда, это не похоже на проблему, которая может возникать очень часто. Но так как ввод явных using объявлений / директив (например, using namespace std;) не является большой проблемой для большинства людей, она полностью решает эту проблему и, в любом случае, потребуется для переносимой разработки, их использование (хех) кажется разумным способом делать вещи.

ПРИМЕЧАНИЕ: Как указывал Клаим, вы никогда ни при каких обстоятельствах не захотите захотеть использовать автоматический поиск в заголовочном файле, так как это немедленно предотвратит использование вашего модуля. в то же время, как любой модуль, содержащий конфликтующее имя. (Это просто логическое продолжение того, почему вы не делаете using namespace xyz; внутри заголовков в C ++ в его нынешнем виде.)

0 голосов
/ 12 февраля 2009

Все зависит от вашего определения «правильной вещи». Правильно ли для компилятора угадывать ваши намерения, если есть только одно совпадение?

Есть аргументы для обеих сторон.

...