Почему не может быть никаких неявных преобразований?

14

Насколько я понимаю, неявные преобразования могут вызвать ошибки.

Но это не имеет смысла - не должны ли нормальные преобразования также вызывать ошибки?

Почему бы не иметь

len(100)

работать на языке, интерпретируя (или компилируя) его как

len(str(100))

тем более, что это единственный способ (я знаю) для его работы. Язык знает, что это за ошибка, почему бы не исправить ее?

Для этого примера я использовал Python, хотя я чувствую, что для чего-то такого маленького он в основном универсален.

Quelklef
источник
2
perl -e 'print length(100);'печатает 3.
2
И, таким образом, природа языка и его система типов. Это часть дизайна Python.
2
Это не исправит это, потому что не знает твоей амнезии. Может быть, вы хотели сделать что-то совершенно другое. как подать в суд на петлю, но никогда не делал ничего подобного программированию. поэтому, если он сам исправит это, пользователь не узнает, что он ошибался, или что реализация не будет делать то, что он ожидает.
Зайбис
5
@PieCrust Как Python должен конвертировать? Это не правда , что все возможные преобразования будут возвращать один и тот же результат.
Бакуриу
7
@PieCrust: строки и массивы являются итеративными. почему был strбы неявный способ преобразовать int в итерируемое? как насчет range? или bin( hex, oct) или chr(или unichr)? Все эти возвращаемые предметы, даже если вам strкажется наиболее очевидным в этой ситуации.
njzk2

Ответы:

37

Для чего это стоит len(str(100)), len(chr(100))и len(hex(100))все разные. strэто не единственный способ заставить его работать, так как в Python есть несколько разных преобразований из целого числа в строку. Один из них, конечно, самый распространенный, но он не обязательно говорит о том, что вы имели в виду. Неявное преобразование буквально означает «само собой разумеется».

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

Вот почему (в большинстве случаев) Python предпочитает явное неявному, он предпочитает не рисковать. Основной случай, когда Python выполняет приведение типов, находится в арифметике. Это позволяет , 1 + 1.0потому что альтернатива была бы слишком раздражает жить, но это не позволяет , 1 + "1"потому что он считает , что вы должны указать ли вы имеете в виду int("1"), float("1"), ord("1"), str(1) + "1", или что - то еще. Он также не позволяет (1,2,3) + [4,5,6], даже если он может определять правила для выбора типа результата, так же, как он определяет правила для выбора типа результата 1 + 1.0.

Другие языки не согласны и имеют много неявных преобразований. Чем больше они включают, тем менее очевидными они становятся. Попробуйте запомнить правила из стандарта C для "целочисленных повышений" и "обычных арифметических преобразований" перед завтраком!

Стив Джессоп
источник
+1 Для демонстрации того, как исходное предположение, которое lenможет работать только с одним типом, в корне неверно.
KChaloux
2
@KChaloux: на самом деле, в моем примере str, chrи hexвсе возвращают один и тот же тип! Я пытался придумать другой тип, конструктор которого может принимать только int, но я еще ничего не придумал. Идеальным будет какой-то контейнер, Xгде len(X(100)) == 100;-) numpy.zerosкажется немного неясным.
Стив Джессоп
2
Примеры возможных проблем увидеть это: docs.google.com/document/d/... и destroyallsoftware.com/talks/wat
Jens Шаудером
1
Неявное преобразование (я думаю , это называется принуждением) может вызвать неприятные вещи , как имеющие a == b, b == c, но a == cне быть правдой.
bgusach
Отличный ответ. Все, что я хотел сказать, только лучше сформулировано! Моя единственная небольшая ошибка в том, что целочисленные продвижения на языке C довольно просты по сравнению с запоминанием правил приведения JavaScript или наложением головы на базу кода C ++ с небрежным использованием неявных конструкторов.
GrandOpener
28

Насколько я понимаю, неявные преобразования могут вызвать ошибки.

Вам не хватает слова: неявные преобразования могут вызвать ошибки во время выполнения .

Для такого простого случая, как вы показываете, довольно ясно, что вы имели в виду. Но языки не могут работать на случаи. Им нужно работать с правилами. Для многих других ситуаций неясно, допустил ли программист ошибку (используя неправильный тип) или же программист хотел сделать то, что, как предполагает код, он хотел сделать.

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

Следует отметить, что неявные преобразования делают компилятор немного более сложным. Вы должны быть более осторожны с циклами (давайте попробуем это преобразование из A в B; упс, которое не сработало, но есть преобразование из B в A!, А затем вам нужно также позаботиться о циклах размера C и D ), которые это небольшая мотивация, чтобы избежать неявных преобразований.

Telastyn
источник
В основном речь шла о функциях, которые могут работать только с одним типом, что делает преобразование очевидным. Какой будет работать с несколькими типами, и тип, который вы вводите, имеет значение? print ("295") = print (295), так что это не будет иметь значения, за исключением переменных. То, что вы сказали, имеет смысл, за исключением 3-го абзаца ... Вы можете повторить?
Quelklef
30
@PieCrust - 123 + "456", вы хотели "123456" или 579? Языки программирования не имеют контекста, поэтому им трудно «разобраться», поскольку им необходимо знать контекст, в котором выполняется добавление. Какой параграф неясен?
Теластин
1
Кроме того, может быть, интерпретация как Base-10 не то, что вы хотели. Да, неявные преобразования - это хорошо , но только там, где они не могут скрыть ошибку.
Дедупликатор
13
@PieCrust: Честно говоря, я никогда не ожидал len(100)бы возвращения 3. Я бы посчитал более понятным вычисление количества бит (или байтов) в представлении 100.
user541686
3
Я из @Mehrdad, из вашего примера ясно, что вы думаете, что на 100% очевидно, что len(100)должно дать вам количество символов десятичного представления числа 100. Но на самом деле это тонна предположений, которые вы делаете, не подозревая их. Вы можете привести веские аргументы, почему число битов, байтов или символов шестнадцатеричного представления ( 64-> 2 символа) должно быть возвращено len().
funkwurm
14

Неявные преобразования вполне возможно сделать. Ситуация, когда вы попадаете в беду, это когда вы не знаете, как что-то должно работать.

Пример этого можно увидеть в Javascript, где +оператор работает по-разному в разное время.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

Если один из аргументов является строкой, то +оператор является конкатенацией строк, в противном случае это сложение.

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

Еще один способ справиться с этим - из базового наследия (из которого следует perl - см. Программирование сложно, давайте приступим к написанию сценариев ... )

В Basic lenфункция имеет смысл вызываться только для String (документы для Visual Basic : «Любое допустимое выражение String или имя переменной. Если Expression имеет тип Object, функция Len возвращает размер, так как он будет записан в файл функция FilePut. ").

Perl следует этой концепции контекста. Путаница , которая существует в JavaScript с неявным преобразованием типов для +оператора является иногда сложением и иногда конкатенации не происходят в Perl , потому что +это всегда сложение и .это всегда конкатенация.

Если что-то используется в скалярном контексте, это скаляр (например, использование списка в качестве скаляра, список ведет себя так, как будто это число, соответствующее его длине). Если вы используете строковый оператор ( eqдля проверки равенства, cmpдля сравнения строк), скаляр используется так, как если бы он был строкой. Аналогично, если что-то использовалось в математическом контексте ( ==для проверки на равенство и <=>для численного сравнения), скаляр используется, как если бы это было число.

Основное правило для всего программирования - «делай то, что удивляет человека меньше всего». Это не значит, что там нет сюрпризов, но попытка удивить человека меньше всего.

Переходя к близкому родственнику perl - php, бывают ситуации, когда оператор может воздействовать на что-либо в строковом или числовом контексте, и поведение может удивлять людей. ++Оператор является одним из таких примеров. На цифрах он ведет себя точно так, как ожидалось. При воздействии на строку, например "aa", она увеличивает строку ( $foo = "aa"; $foo++; echo $foo;печатает ab). Он также перевернется так, что azпри увеличении становится ba. Это не особенно удивительно.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

( идеон )

Это распечатывает:

3d8
3d9
3e0
4

Добро пожаловать в опасность неявных преобразований и операторов, действующих по-разному в одной строке. (Perl обрабатывает этот кодовый блок немного по-другому - он решает, что "3d8"когда ++оператор применяется, является числовым значением с самого начала и сразу переходит 4( ideone ) - это поведение хорошо описано в perlop: Автоинкремент и Авто-декремент )

Теперь, почему один язык делает что-то одно, а другой делает это по-другому, доходит до дизайнерских мыслей дизайнеров. Философия Perl заключается в том, что существует несколько способов сделать это - и я могу придумать несколько способов выполнения некоторых из этих операций. С другой стороны, у Python есть философия, описанная в PEP 20 - Zen of Python, которая гласит (среди прочего): «Должен быть один - и предпочтительно только один - очевидный способ сделать это».

Эти конструктивные различия привели к разным языкам. Есть один способ получить длину числа в Python. Неявное обращение идет вразрез с этой философией.

Связанное чтение: Почему в Ruby нет неявного преобразования Fixnum в String?

Сообщество
источник
ИМХО, у хорошего языка / фреймворка часто должно быть несколько способов делать вещи, которые по-разному обрабатывают общие угловые случаи (лучше обрабатывать общий угловой случай один раз на языке или структуре, чем 1000 раз в 1000 программах); тот факт, что два оператора в большинстве случаев делают одно и то же, не следует считать плохой вещью, если в случае различий в поведении у каждого варианта есть свои преимущества. Не должно быть трудно сравнивать две числовые переменные xи yтаким образом, чтобы получить отношение эквивалентности или ранжирование, но операторы сравнения IIRC Python ...
суперкат
... не реализуют ни отношений эквивалентности, ни ранжирования, и Python не предоставляет удобных операторов, которые это делают.
суперкат
12

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

Результатом является полная анархия, где добавление «4a» к 6 составляет 6,16667.

Почему? Ну, потому что первая из двух переменных является числом, поэтому результат будет числовым. «4а» анализируется как дата, и рассматривается как «4 утра». 4 утра - 4:00 / 24:00 или 1/6 дня (0,16667). Добавьте к 6, и вы получите 6.16667.

Списки по умолчанию содержат символы-разделители запятой, поэтому, если вы когда-либо добавляете элемент в список, содержащий запятую, вы просто добавляете два элемента. Кроме того, списки являются тайными строками, поэтому они могут быть проанализированы как даты, если они содержат только 1 элемент.

Сравнение строк проверяет, можно ли сначала проанализировать обе строки по датам. Потому что нет типа даты, он делает это. То же самое для чисел и строк, содержащих числа, восьмеричные обозначения и логические значения («истина», «да» и т. Д.)

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

Конечно, вы могли бы исправить неявные преобразования, вызвав явные функции, такие как DateCompare ... но тогда вы потеряли «преимущества» неявного преобразования.


Для ColdFusion это способ помочь разработчикам расширить возможности. Особенно, когда все эти разработчики привыкли к HTML (ColdFusion работает с тегами, он называется CFML, позже они также добавили скрипты через <cfscript>, где вам не нужно добавлять теги для всего). И это хорошо работает, когда вы просто хотите, чтобы что-то работало. Но когда вам нужно, чтобы вещи происходили более точно, вам нужен язык, который отказывается делать неявные преобразования для всего, что выглядит так, как будто это могло быть ошибкой.

Pimgd
источник
1
вау, "полная анархия" действительно!
hoosierEE
7

Вы говорите, что неявные преобразования могут быть хорошей идеей для однозначных операций, например int a = 100; len(a), когда вы явно хотите преобразовать int в строку перед вызовом.

Но вы забываете, что эти вызовы могут быть синтаксически однозначными, но они могут представлять опечатку, сделанную программистом, который намеревался передать a1, что является строкой. Это надуманный пример, но с IDE, обеспечивающими автозаполнение имен переменных, эти ошибки случаются.

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

Проверьте горе Javascript со всеми неявными преобразованиями, выполняемыми ==, настолько, что многие теперь рекомендуют придерживаться оператора no-implicit-translation ===.

Авнер Шахар-Каштан
источник
6

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

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

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

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

GrandOpener
источник
3

Неявные преобразования могут быть настоящей болью для работы. В PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

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

Эсбен Сков Педерсен
источник
2

Явные приведения важны, потому что они проясняют ваши намерения. Прежде всего, использование явных приведений рассказывает историю тому, кто читает ваш код. Они показывают, что ты намеренно сделал то, что сделал. Более того, то же самое относится и к компилятору. Следующее недопустимо в C # например

double d = 3.1415926;
// code elided
int i = d;

В результате произойдет потеря точности, что может быть ошибкой. Следовательно, компилятор отказывается компилировать. Используя явное приведение, вы говорите компилятору: «Эй, я знаю, что делаю». И он пойдет: "Хорошо!" И скомпилировать.

Пол Керчер
источник
1
На самом деле, я больше не люблю большинство явных приведений, чем неявные; ИМХО, единственное, что (X)yдолжно означать: «Я думаю, что Y обратим в X; сделай преобразование, если это возможно, или брось исключение, если нет»; если преобразование выполнено успешно, нужно уметь предсказать, каким будет результирующее значение *, без необходимости знать какие-либо особые правила преобразования из типа yв тип X. Если попросить преобразовать -1,5 и 1,5 в целые числа, некоторые языки будут давать (-2,1); некоторые (-2,2), некоторые (-1,1) и некоторые (-1,2). В то время как правила С вряд ли неясны ...
суперкат
1
... кажется, что такое преобразование было бы более целесообразно выполнять с помощью метода, а не с помощью приведения (очень плохо .NET не включает в себя эффективные функции округления и преобразования, а Java перегружены несколько глупо).
суперкат
Вы говорите компилятору: « Я думаю, что знаю, что делаю».
gnasher729
@ gnasher729 В этом есть доля правды :)
Пол Керчер
1

Языки, которые поддерживают неявные преобразования / приведение или слабую типизацию, как это иногда называют, сделают для вас предположения, которые не всегда соответствуют поведению, которое вы намеревались или ожидали. Разработчики языка могли иметь такой же мыслительный процесс, как и вы для определенного типа преобразования или серии преобразований, и в этом случае вы никогда не увидите проблемы; однако, если они этого не сделали, то ваша программа потерпит неудачу.

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

На днях мой друг спросил своего 2-летнего, что такое 20 + 20, и он ответил 2020. Я сказал своему другу: «Он собирается стать программистом Javascript».

В Javascript:

20+20
40

20+"20"
"2020"

Таким образом, вы можете видеть проблемы, которые может создавать неявное приведение, и почему это не то, что можно просто исправить. Я бы сказал, что исправление заключается в использовании явных приведений.

Фред Томсен
источник