Чому не може бути явних конверсій?


14

Як я розумію, неявна конверсія може спричинити помилки.

Але це не має сенсу - чи не повинні нормальні конверсії також викликати помилки?

Чому б не мати

len(100)

робота мовою, що її інтерпретують (або компілюють) як

len(str(100))

тим більше, що це єдиний спосіб (який я знаю) для нього. Мова знає, що таке помилка, чому не виправити її?

Для цього прикладу я використовував Python, хоча я вважаю, що для чогось такого маленького це в основному універсально.


2
perl -e 'print length(100);'принти 3.

2
А отже характер мови та її тип системи. Це частина дизайну пітона.

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всі повертаються одного типу! Я намагався придумати інший тип, конструктор якого може взяти лише інт, але я ще нічого не придумав. Ідеально було б якийсь контейнер, Xде len(X(100)) == 100;-) numpy.zerosздається трохи незрозумілим.
Стів Джессоп

2
Для прикладів можливих проблем дивіться це: docs.google.com/document/d/… та killallsoftware.com/talks/wat
Єнс Шодер

1
Неявне перетворення (я думаю , це називається примусом) може викликати неприємні речі , як мають a == b, b == c, але a == cже не бути правдою.
bgusach

Відмінна відповідь. Все, що я хотів сказати, тільки сформулював краще! Моя єдина незначна суперечка - це те, що рекламні кампанії на цілі числа є досить простими порівняно із запам'ятовуванням правил примусу JavaScript або обертанням голови навколо кодової бази C ++ при необережному використанні неявних конструкторів.
GrandOpener

28

Як я розумію, неявна конверсія може спричинити помилки.

Вам не вистачає слова: неявні перетворення можуть спричинити помилки під час виконання .

Для простого випадку, як ви показуєте, цілком зрозуміло, що ви мали на увазі. Але мови не можуть працювати у випадках. Їм потрібно працювати з правилами. У багатьох інших ситуаціях незрозуміло, чи програміст допустив помилку (використовуючи неправильний тип) чи програміст мав робити те, що передбачає код, який вони мали намір зробити.

Якщо код вважає неправильним, ви отримуєте помилку виконання. Оскільки вони нудні відстежувати, багато мов помиляються в сторону того, що вам сказали, що ви заплуталися і дозволяєте розповісти комп’ютеру, що ви насправді мали на увазі (виправте помилку або явно зробіть конверсію). Інші мови здогадуються, оскільки їх стиль піддається швидкому та легкому коду, який простіше налагодити.

Варто зазначити, що неявні перетворення роблять компілятор трохи складнішим. Вам потрібно бути більш уважними щодо циклів (спробуємо це перетворення з А в В; ой, що не спрацювало, але є перехід від В до А! І тоді вам також потрібно потурбуватися про цикли розміру С і D ), які це невелика мотивація уникати неявних перетворень.


Переважно говорили про функції, які можуть працювати лише одного типу, що робить перетворення очевидним. Що було б, яке б працювало з декількома типами, а тип, який ви вводите, має значення? print ("295") = print (295), щоб це не змінило, за винятком змінних. Те, що ви сказали, має сенс, крім 3-го абзацу ... Чи можете ви ще раз зазначити?
Квеклеф

30
@PieCrust - 123 + "456", ти хотів "123456" чи 579? Мови програмування не мають контексту, тому їм важко "розібратися в цьому", оскільки їм потрібно знати контекст, в якому робиться додавання. Який абзац незрозумілий?
Теластин

1
Крім того, можливо, інтерпретація як основа-10 - це не те, чого ви хотіли. Так, неявна конверсія - це гарна річ , але лише там, де вони не можуть приховати помилку.
Дедуплікатор

13
@PieCrust: Якщо чесно, я ніколи не очікував len(100)би повернутися 3. Мені було б набагато інтуїтивніше обчислити кількість бітів (або байтів) у поданні 100.
користувач541686

3
Я з @Mehrdad, з вашого прикладу видно, що ви вважаєте, що це на 100% очевидно, що len(100)має дати вам кількість символів десяткового представлення числа 100. Але це насправді багато припущень, які ви робите, не знаючи їх. Ви можете зробити вагомі аргументи, чому 64слід повернути кількість біт або байтів або символів шістнадцяткового зображення ( -> 2 символи) len().
asontu

14

Неявні перетворення цілком можливо зробити. Ситуація, коли ти потрапляєш у біду, - це коли ти не знаєш, в якому напрямку щось має працювати.

Приклад цього можна побачити в Javascript, де +оператор працює в різні періоди по-різному.

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

Якщо одним з аргументів є рядок, то +оператор - це об'єднання рядків, інакше це додавання.

Якщо вам дають аргумент і не знаєте, чи це рядок або ціле число, і ви хочете зробити з ним доповнення, це може бути безладно.

Ще один спосіб вирішити це - з базової спадщини (ця хитрість випливає з - див. Програмування важко, Давайте підемо сценарій ... )

У Основному lenфункція має сенс викликати лише String (документи для візуального 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";

( ideone )

Це видає:

3d8
3d9
3e0
4

Ласкаво просимо до небезпеки неявних перетворень та операторів, які діють на одній і тій же строці. (Perl обробляє цей код блоком трохи інакше - він вирішує, що "3d8"коли застосований ++оператор, є числовим значенням з самого початку і переходить 4відразу ( ideone ) - така поведінка добре описана в perlop: Auto-increment і Auto-decrement )

Тепер, чому одна мова робить щось по-іншому, а інша робить іншим способом, потрапляє до дизайнерських думок дизайнерів. Філософія Perl полягає в тому, що це існує більше ніж один спосіб - і я можу придумати декілька способів зробити деякі з цих операцій. З іншого боку, у Python є філософія, описана в PEP 20 - Zen of Python, де сказано (серед іншого): "Має бути один - і бажано лише один - очевидний спосіб зробити це".

Ці відмінності в дизайні призвели до різних мов. Існує один спосіб отримати довжину числа в Python. Неявне перетворення суперечить цій філософії.

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


ІМХО, хороша мова / структура повинна часто мати кілька способів робити речі, які по-різному обробляють загальні випадки кута (краще обробляти загальний кутовий випадок один раз мовою або рамками, ніж 1000 разів за 1000 програм); той факт, що два оператори роблять одне і те саме, більшу частину часу не слід вважати поганою справою, якщо, коли їх поведінка відрізняється, було б корисно для кожної зміни. Це не повинно бути важко порівнювати два числових змінних xі yтаким чином, з отриманням відносини або рейтинг еквівалентності, але оператори порівняння IIRC мови Python ...
SUPERCAT

... не реалізувати ні відношення еквівалентності, ні ранжування, і Python не забезпечує зручних операторів, які це роблять.
supercat

12

ColdFusion зробив більшу частину цього. Він визначає набір правил для обробки непрямих перетворень, має єдиний тип змінної, і там ви йдете.

Результат - загальна анархія, де додавання "4a" до 6 становить 6,16667.

Чому? Ну, оскільки перша з двох змінних - це число, тож результат буде числовим. "4a" розбирається як дата і розглядається як "4 AM". 4 ранку - 4: 00/24: 00, або 1/6 дня (0,16667). Додайте до 6 і отримаєте 6,16667.

У списках є символи розділення за замовчуванням кома, тому якщо ви коли-небудь додаєте елемент до списку, що містить кому, ви просто додали два елементи. Також списки є таємно рядками, тому їх можна розібрати як дати, якщо вони містять лише 1 елемент.

Порівняння рядків перевіряє, чи можна обидва рядки проаналізувати спочатку датами. Оскільки немає дати, це робить це. Те саме, що стосується чисел і рядків, що містять числа, восьмеричні позначення та булеві букви ("правда" та "так" тощо)

Замість того, щоб швидко виходити з ладу та повідомляти про помилку, натомість ColdFusion обробляє перетворення типів даних для вас. І не в хороший спосіб.

Звичайно, ви можете виправити неявні перетворення, викликаючи явні функції, такі як DateCompare ... але тоді ви втратили "переваги" неявного перетворення.


Для ColdFusion це спосіб допомогти розробити розробників. Особливо, коли всі ці розробники звикли до HTML (ColdFusion працює з тегами, він називається CFML, пізніше вони також додали сценарії через <cfscript>, де вам не потрібно додавати теги для всього). І це добре працює, коли ви просто хочете, щоб щось працювало. Але коли вам потрібно, щоб все відбувалося більш точним чином, вам потрібна мова, яка відмовляється робити неявні перетворення для всього, що виглядає так, як це могло бути помилкою.


1
вау, "тотальна анархія" справді!
hoosierEE

7

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

Але ви забуваєте, що ці дзвінки можуть бути синтаксично однозначними, але вони можуть представляти друкарський помилок, зроблений програмістом, який мав намір пройти a1, що є рядком. Це надуманий приклад, але з IDE, що надають автозаповнення для імен змінних, ці помилки трапляються.

Система типів має на меті допомогти нам уникнути помилок, а для мов, які обирають більш жорстку перевірку типу, неявні перетворення це підривають.

Ознайомтеся з неприємностями Javascript з усіма неявними перетвореннями, виконаними ==, настільки, що багато хто зараз рекомендує дотримуватися оператора no-implicit-convert ===.


6

Розгляньте на мить контекст вашої заяви. Ви кажете, що це "єдиний спосіб", який він міг би працювати, але ви справді впевнені, що він буде працювати так? Як що до цього:

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

Як вже згадували інші, у вашому дуже конкретному прикладі компілятору здається простим зрозуміти, що ви мали на увазі. Але в більш широкому контексті складніших програм часто зовсім не очевидно, чи ви мали намір ввести рядок, або набрали одне ім'я змінної для іншої, або викликали неправильну функцію, або будь-яку з десятків інших потенційних логічних помилок .

У випадку, коли перекладач / укладач здогадується про те, що ви мали на увазі, іноді це магічно працює, а інший час ви витрачаєте години налагоджуючи щось, що загадково не працює без видимих ​​причин. У середньому випадку набагато безпечніше сказати, що твердження не має сенсу, і витратити кілька секунд, виправляючи його до того, що ви насправді мали на увазі.


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). Поки правила С навряд чи незрозумілі ...
supercat

1
... такий вид перетворення здається, що це було б більш доцільно проводити методом, ніж за допомогою передачі (занадто погано.
supercat

Ви говорите компілятору " Я думаю, я знаю, що роблю".
gnasher729

@ gnasher729 У цьому є якась правда :)
Пол Кертчер

1

Мови, які підтримують неявні перетворення / залишки чи слабке введення тексту, як це іноді називають, дозволять зробити для вас припущення, які не завжди відповідають тій поведінці, яку ви планували чи очікували. Модельєри мови, можливо, мали той самий процес мислення, як і ви певного типу конверсії чи серії перетворень, і в цьому випадку ви ніколи не побачите проблеми; однак якщо вони цього не зробили, то ваша програма вийде з ладу.

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

Днями мій друг запитав у свого дворічного віку, що таке 20 + 20, і він відповів 2020. Я сказав своєму другові: "Він стане програмістом Javascript".

У Javascript:

20+20
40

20+"20"
"2020"

Таким чином, ви можете бачити проблеми, які можуть створювати неявні касти, і чому це не те, що можна просто виправити. Виправлення, яке я б заперечив, полягає у використанні явних кастів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.