Jak rozumiem, niejawne konwersje mogą powodować błędy.
Ale to nie ma sensu - czy zatem normalne konwersje nie powinny również powodować błędów?
Dlaczego nie mieć?
len(100)
praca przez język interpretujący (lub kompilujący) jako
len(str(100))
zwłaszcza, że jest to jedyny (wiem) sposób na to, aby działał. Język wie, jaki jest błąd, dlaczego go nie naprawić?
W tym przykładzie użyłem Pythona, choć wydaje mi się, że dla czegoś tak małego jest on w zasadzie uniwersalny.
programming-languages
type-conversion
Quelklef
źródło
źródło
perl -e 'print length(100);'
wydruki 3.str
być niejawnym sposobem na przekształcenie int w iterowalną? jak orange
? lubbin
(hex
,oct
) lubchr
(lubunichr
)? Wszystkie te zwroty iteracyjne, nawet jeślistr
wydaje Ci się to najbardziej oczywiste w tej sytuacji.Odpowiedzi:
Za to, co jest warte
len(str(100))
,len(chr(100))
ilen(hex(100))
wszystkie są różne. niestr
jest jedynym sposobem na to, aby działał, ponieważ w Pythonie istnieje więcej niż jedna konwersja z liczby całkowitej na ciąg. Jeden z nich jest oczywiście najczęstszy, ale niekoniecznie trzeba powiedzieć, że o to ci chodziło. Ukryta konwersja dosłownie oznacza „to oczywiste”.Jednym z praktycznych problemów z niejawnymi konwersjami jest to, że nie zawsze jest oczywiste, która konwersja zostanie zastosowana, gdy istnieje kilka możliwości, a to powoduje, że czytelnicy popełniają błędy podczas interpretacji kodu, ponieważ nie potrafią znaleźć prawidłowej niejawnej konwersji. Wszyscy zawsze mówią, że ich zamierzeniem jest „oczywista interpretacja”. Jest to dla nich oczywiste, ponieważ o to im chodziło. Dla kogoś innego może to nie być oczywiste.
Dlatego (przez większość czasu) Python woli jawnie niż dorozumiany, woli nie ryzykować. Głównym przypadkiem, w którym Python robi przymus typu, jest arytmetyka. Pozwala
1 + 1.0
ponieważ alternatywą byłoby zbyt uciążliwe, aby żyć, ale to nie pozwala1 + "1"
, ponieważ uważa, że nie powinno być określenie, czy to znaczyint("1")
,float("1")
,ord("1")
,str(1) + "1"
, lub coś innego. To również nie pozwala(1,2,3) + [4,5,6]
, nawet jeśli może zdefiniować reguły wyboru typu wyniku, podobnie jak definiuje reguły wyboru typu wyniku1 + 1.0
.Inne języki nie zgadzają się i mają wiele niejawnych konwersji. Im więcej zawierają, tym mniej oczywiste stają się. Spróbuj zapamiętać reguły ze standardu C dotyczące „promocji całkowitych” i „zwykłych konwersji arytmetycznych” przed śniadaniem!
źródło
len
może działać tylko z jednym typem, jest zasadniczo wadliwe.str
,chr
ihex
wszystkie zwracają ten sam typ! Próbowałem wymyślić inny typ, którego konstruktor może przyjąć tylko int, ale jeszcze niczego nie wymyśliłem. Idealny byłby jakiś pojemnik, wX
którymlen(X(100)) == 100
;-)numpy.zeros
wydaje się nieco niejasny.a == b
,b == c
alea == c
nie jest prawdą.Brakuje Ci słowa: niejawne konwersje mogą powodować błędy w czasie wykonywania .
W przypadku tak prostego przypadku, jak pokazujesz, jasne jest, co miałeś na myśli. Ale języki nie mogą działać na przypadkach. Muszą pracować z regułami. W wielu innych sytuacjach nie jest jasne, czy programista popełnił błąd (używając niewłaściwego typu), czy też programista chciał zrobić to, co zakłada kod.
Jeśli kod zakłada błędnie, pojawia się błąd czasu wykonania. Ponieważ śledzenie ich jest żmudne, wiele języków nie zgadza się z informacją, że popełniłeś błąd i pozwalasz komputerowi powiedzieć, co naprawdę miałeś na myśli (napraw błąd lub jawnie dokonaj konwersji). Zgadują inne języki, ponieważ ich styl nadaje się do szybkiego i łatwego kodu, który jest łatwiejszy do debugowania.
Należy zauważyć, że niejawne konwersje sprawiają, że kompilator jest nieco bardziej złożony. Musisz bardziej uważać na cykle (spróbujmy tej konwersji z A na B; ups, które nie działały, ale istnieje konwersja z B na A !, a następnie musisz się martwić o cykle wielkości C i D ), które to niewielka motywacja do unikania niejawnych konwersji.
źródło
len(100)
się powrotu3
. O wiele bardziej intuicyjne byłoby obliczanie liczby bitów (lub bajtów) w reprezentacji100
.len(100)
powinno dać ci liczbę znaków dziesiętnej reprezentacji liczby 100. Ale to jest naprawdę mnóstwo założeń, których nie przyjmujesz z nich. Można argumentować, dlaczego liczba bitów, bajtów lub znaków reprezentacji szesnastkowej (64
-> 2 znaki) powinna być zwracanalen()
.Możliwe są niejawne konwersje. Sytuacja, w której masz kłopoty, polega na tym, że nie wiesz, w jaki sposób coś powinno działać.
Przykładem tego jest Javascript, w którym
+
operator działa w różny sposób w różnym czasie.Jeśli jeden z argumentów jest łańcuchem, to
+
operator jest łańcuchem łączenia, w przeciwnym razie jest to dodawanie.Jeśli otrzymujesz argument i nie wiesz, czy jest to ciąg znaków, czy liczba całkowita i chcesz dodać do niego argument, może to być trochę bałaganu.
Innym sposobem radzenia sobie z tym jest z Podstawowego dziedzictwa (z którego pochodzi perl - patrz Programowanie jest trudne, chodźmy skrypty ... )
W Basicu
len
funkcja ma sens tylko przywoływana na String (docs dla visual basic : „Dowolne prawidłowe wyrażenie String lub nazwa zmiennej. Jeśli Expression jest typu Object, funkcja Len zwraca rozmiar, który zostanie zapisany w pliku przez funkcja FilePut. ”).Perl podąża za tą koncepcją kontekstu. Zamieszanie występujące w JavaScript z niejawną konwersją typów dla
+
operatora będącego czasem dodawaniem, a czasem konkatenacją nie występuje w Perlu, ponieważ zawsze+
jest dodawaniem i zawsze jest konkatenacją..
Jeśli coś jest używane w kontekście skalarnym, jest to skalar (np. Używając listy jako skalara, lista zachowuje się tak, jakby była liczbą odpowiadającą jej długości). Jeśli używasz operatora łańcucha (
eq
do testu równości,cmp
do porównania łańcucha), skalar jest używany tak, jakby był łańcuchem. Podobnie, jeśli coś zostało użyte w kontekście matematycznym (==
do testu równości i<=>
porównania numerycznego), skalar jest używany tak, jakby był liczbą.Podstawową zasadą wszystkich programów jest „robienie rzeczy, które najmniej zaskakują człowieka”. Nie oznacza to, że nie ma w tym niespodzianek, ale staramy się jak najmniej zaskoczyć osobę.
Idąc do bliskiego kuzyna perl - php, zdarzają się sytuacje, w których operator może działać na coś w kontekście łańcuchowym lub liczbowym, a zachowanie może być zaskakujące dla ludzi.
++
Operator jest jednym z przykładów. W przypadku liczb zachowuje się dokładnie tak, jak oczekiwano. Podczas działania na łańcuch, na przykład"aa"
, zwiększa łańcuch ($foo = "aa"; $foo++; echo $foo;
drukujeab
). Będzie również przewracał się, abyaz
po zwiększeniu stał sięba
. Nie jest to jeszcze szczególnie zaskakujące.( ideone )
To drukuje:
Witaj w niebezpieczeństwach niejawnych konwersji i działania różnych operatorów na tym samym łańcuchu. (Uchwyty Perl że kod blokują trochę inaczej - uzna, że
"3d8"
gdy++
operator jest stosowana jest wartość liczbowa od samego początku i idzie4
od razu ( ideone ) - takie zachowanie jest dobrze opisana w perlop: Auto-inkrementacja i Auto-ubytek )Teraz, dlaczego jeden język robi coś w jedną stronę, a inny w inny sposób, przechodzi do myśli projektowych projektantów. Filozofią Perla jest to, że można to zrobić na więcej niż jeden sposób - i mogę wymyślić kilka sposobów wykonania niektórych z tych operacji. Z drugiej strony Python ma filozofię opisaną w PEP 20 - Zen of Python, która stwierdza (między innymi): „Powinien istnieć jeden - a najlepiej tylko jeden - oczywisty sposób”.
Te różnice projektowe doprowadziły do różnych języków. Jest jeden sposób na uzyskanie długości liczby w Pythonie. Niejawna konwersja jest sprzeczna z tą filozofią.
Literatura pokrewna: Dlaczego Ruby nie ma niejawnej konwersji Fixnum na String?
źródło
x
orazy
w taki sposób, aby wydajność relacją równoważności lub ranking, ale porównania operatorzy IIRC Pythona ...ColdFusion zrobił większość tego. Definiuje zestaw reguł do obsługi niejawnych konwersji, ma jeden typ zmiennej i gotowe.
Rezultatem jest całkowita anarchia, w której dodanie „4a” do 6 to 6.16667.
Dlaczego? Ponieważ pierwsza z dwóch zmiennych jest liczbą, więc wynik będzie liczbowy. „4a” jest analizowany jako data i jest postrzegany jako „4 rano”. 4 rano jest 4: 00/24: 00 lub 1/6 dnia (0,16667). Dodaj do 6, a otrzymasz 6.16667.
Listy mają domyślne znaki oddzielające przecinek, więc jeśli kiedykolwiek dodasz element do listy zawierającej przecinek, po prostu dodasz dwa elementy. Ponadto listy są potajemnie łańcuchami, dzięki czemu mogą być analizowane jako daty, jeśli zawierają tylko 1 element.
Porównanie ciągów sprawdza, czy oba ciągi można najpierw przeanalizować z datami. Ponieważ nie ma typu daty, robi to. To samo dotyczy liczb i ciągów zawierających liczby, notację ósemkową i logiczne („prawda” i „tak” itp.)
Zamiast szybko zawieść i zgłosić błąd, zamiast tego ColdFusion zajmie się konwersją typu danych. I nie w dobry sposób.
Oczywiście można naprawić niejawne konwersje, wywołując funkcje jawne, takie jak DateCompare ... ale wtedy utracisz „korzyści” z niejawnej konwersji.
W przypadku ColdFusion jest to sposób na wsparcie programistów. Zwłaszcza, gdy wszyscy ci programiści są przyzwyczajeni do HTML (ColdFusion działa z tagami, nazywa się to CFML, później dodali także skrypty za pośrednictwem
<cfscript>
, gdzie nie trzeba dodawać tagów do wszystkiego). I działa dobrze, gdy chcesz po prostu coś uruchomić. Ale gdy potrzebujesz rzeczy, które mają się wydarzyć w bardziej precyzyjny sposób, potrzebujesz języka, który odmawia dokonywania niejawnych konwersji dla wszystkiego, co wygląda na to, że mogło być błędem.źródło
Mówisz, że niejawne konwersje mogą być dobrym pomysłem dla operacji, które są jednoznaczne, na przykład
int a = 100; len(a)
, gdy oczywiście zamierzasz przekonwertować int na ciąg przed wywołaniem.Ale zapominasz, że te rozmowy mogą być składniowo jednoznaczne, ale mogą one reprezentować literówkę wykonany przez programistę, który ma na celu przekazać
a1
, który jest ciągiem. Jest to wymyślony przykład, ale z IDE zapewniającymi autouzupełnianie nazw zmiennych, błędy te się zdarzają.System typów ma na celu pomóc nam uniknąć błędów, a w przypadku języków, które wybierają bardziej rygorystyczne sprawdzanie typu, ukryte konwersje podważają to.
Sprawdź nieszczęścia Javascript ze wszystkimi ukrytymi konwersjami wykonanymi przez ==, tak bardzo, że wielu teraz zaleca trzymanie się operatora no-implicit -version ===.
źródło
Zastanów się przez chwilę nad kontekstem swojego oświadczenia. Mówisz, że to jest „jedyny sposób”, w jaki może to działać, ale czy naprawdę jesteś pewien, że tak będzie? A co z tym:
Jak wspomnieli inni, w bardzo konkretnym przykładzie kompilator wydaje się intuicyjny w zrozumieniu tego, co miałeś na myśli. Ale w szerszym kontekście bardziej skomplikowanych programów często nie jest wcale oczywiste, czy zamierzałeś wprowadzić ciąg, czy literować jedną nazwę zmiennej dla innej, czy wywołać niewłaściwą funkcję, czy którykolwiek z dziesiątek innych potencjalnych błędów logicznych .
W przypadku, gdy interpreter / kompilator zgaduje, co miałeś na myśli, czasami działa to magicznie, a innym razem spędzasz godziny na debugowaniu czegoś, co w tajemniczy sposób nie działa bez wyraźnego powodu. W przeciętnym przypadku o wiele bezpieczniej jest powiedzieć, że to stwierdzenie nie ma sensu, i poświęcić kilka sekund na poprawienie tego, co naprawdę miałeś na myśli.
źródło
Niejawne konwersje mogą być prawdziwym bólem w pracy. W PowerShell:
Nagle potrzebne są dwa razy więcej testów i dwa razy więcej błędów niż w przypadku domniemanej konwersji.
źródło
Jawne obsady są ważne, ponieważ wyjaśniają twoją intencję. Przede wszystkim użycie jawnych rzutów opowiada historię komuś, kto czyta Twój kod. Ujawniają, że celowo zrobiłeś to, co zrobiłeś. Ponadto to samo dotyczy kompilatora. Następujące działania są nielegalne na przykład w języku C #
Obsada sprawi, że stracisz precyzję, co z łatwością może być błędem. Dlatego kompilator odmawia kompilacji. Używając jawnej obsady mówisz kompilatorowi: „Hej, wiem co robię”. I powie: „Okej!” I skompiluj.
źródło
(X)y
powinno znaczyć, to: „Myślę, że Y można przekształcić na X; wykonaj konwersję, jeśli to możliwe, lub wyrzuć wyjątek, jeśli nie”; jeśli konwersja się powiedzie, należy przewidzieć, jaka będzie wynikowa wartość * bez konieczności znajomości specjalnych zasad dotyczących konwersji z typuy
na typX
. Jeśli zostaniesz poproszony o konwersję -1,5 i 1,5 na liczby całkowite, niektóre języki przyniosą (-2,1); niektóre (-2,2), niektóre (-1,1) i niektóre (-1,2). Podczas gdy reguły C nie są wcale niejasne ...Języki, które obsługują niejawne konwersje / rzutowania lub słabe pisanie, jak to się czasem nazywa, przyjmą za Ciebie założenia, które nie zawsze pasują do zamierzonego lub oczekiwanego zachowania. Projektanci języka mogli mieć ten sam proces myślowy, co w przypadku pewnego rodzaju konwersji lub serii konwersji, a w takim przypadku nigdy nie zobaczysz problemu; jednak jeśli nie, Twój program zawiedzie.
Niepowodzenie może jednak być oczywiste. Ponieważ te niejawne rzutowania zachodzą w czasie wykonywania, twój program będzie działał szczęśliwie i zobaczysz problem tylko wtedy, gdy spojrzysz na wynik lub wynik swojego programu. Język, który wymaga jawnych rzutowań (niektórzy nazywają to silnym typowaniem), dawałby ci błąd, zanim program zacznie nawet wykonywać wykonywanie, co jest znacznie bardziej oczywistym i łatwiejszym problemem do naprawienia, że ukryte rzutowania poszły źle.
Któregoś dnia mój przyjaciel zapytał 2-latka, czym jest 20 + 20, i odpowiedział 2020. Powiedziałem mojemu przyjacielowi: „Zostanie programistą Javascript”.
W JavaScript:
W ten sposób można zobaczyć problemy, które mogą tworzyć niejawne rzutowania i dlaczego nie jest to coś, co można po prostu naprawić. Naprawdę, jak twierdzę, jest użycie jawnych rzutowań.
źródło