Czytam kod znacznie częściej niż piszę kod i zakładam, że większość programistów pracujących nad oprogramowaniem przemysłowym to robi. Zakładam, że zaletą wnioskowania typu jest mniejsza szczegółowość i mniej napisany kod. Ale z drugiej strony, jeśli czytasz kod częściej, prawdopodobnie będziesz chciał kodu czytelnego.
Kompilator określa typ; istnieją na to stare algorytmy. Ale prawdziwe pytanie brzmi: dlaczego ja, programista, chciałbym wnioskować o rodzaju moich zmiennych podczas czytania kodu? Czy to nie jest szybsze, aby ktokolwiek po prostu czytał ten typ, niż zastanawiał się, jaki on jest?
Edycja: Podsumowując, rozumiem, dlaczego jest przydatny. Ale w kategorii funkcji językowych widzę to w wiadrze z przeciążeniem operatora - przydatne w niektórych przypadkach, ale wpływające na czytelność, jeśli są nadużywane.
źródło
Odpowiedzi:
Rzućmy okiem na Javę. Java nie może mieć zmiennych z wnioskowanymi typami. Oznacza to, że często muszę przeliterować typ, nawet jeśli dla czytelnika jest zupełnie oczywiste, jaki jest typ:
A czasem pisanie całego typu jest po prostu denerwujące.
Pełne pisanie statyczne przeszkadza mi, programistom. Większość adnotacji typu to powtarzające się wypełnianie wierszy, pozbawione treści zwroty tego, co już wiemy. Lubię jednak pisanie statyczne, ponieważ może to naprawdę pomóc w wykrywaniu błędów, więc używanie pisania dynamicznego nie zawsze jest dobrą odpowiedzią. Wnioskowanie o typach jest najlepsze z obu światów: mogę pominąć niepotrzebne typy, ale nadal mam pewność, że mój program (typ) się sprawdzi.
Chociaż wnioskowanie typu jest naprawdę przydatne w przypadku zmiennych lokalnych, nie powinno się go stosować w publicznych interfejsach API, które muszą być jednoznacznie udokumentowane. A czasem typy naprawdę mają kluczowe znaczenie dla zrozumienia, co dzieje się w kodzie. W takich przypadkach głupotą byłoby polegać wyłącznie na wnioskowaniu typu.
Istnieje wiele języków, które obsługują wnioskowanie typu. Na przykład:
C ++. Te
auto
wyzwalacze słów kluczowych wpisać wnioskowanie. Bez niego wypisywanie rodzajów lambdów lub wpisów w pojemnikach byłoby piekłem.DO#. Można zadeklarować zmienne
var
, co wyzwala ograniczoną formę wnioskowania o typie. Nadal zarządza większością przypadków, w których chcesz wnioskować o typie. W niektórych miejscach możesz całkowicie pominąć ten typ (np. W lambda).Haskell i każdy język w rodzinie ML. Chociaż zastosowany tutaj specyficzny sposób wnioskowania o typie jest dość potężny, nadal często widzisz adnotacje o typach funkcji z dwóch powodów: Pierwszy to dokumentacja, a drugi to sprawdzenie, czy wnioskowanie o typach rzeczywiście znalazło oczekiwane typy. Jeśli występuje rozbieżność, prawdopodobnie występuje jakiś błąd.
źródło
int
, może to być dowolny typ liczbowy, w tym parzystychar
. Nie rozumiem też, dlaczego chcesz przeliterować cały typ,Entry
kiedy możesz po prostu wpisać nazwę klasy i pozwolić twojemu IDE wykonać niezbędny import. Jedynym przypadkiem, gdy musisz przeliterować całe imię, jest posiadanie klasy o tej samej nazwie we własnym pakiecie. Ale i tak wydaje mi się, że to zły projekt.int
przykład, myślałem o (moim zdaniem całkiem rozsądnym zachowaniu) większości języków, w których wnioskuje się o typie. Zwykle wywnioskowaćint
lubInteger
czy cokolwiek to nazywa się w tym języku. Piękno wnioskowania typu polega na tym, że jest ono zawsze opcjonalne; nadal możesz określić inny typ, jeśli go potrzebujesz. Jeśli chodzi oEntry
przykład: dobra uwaga, zastąpię goMap.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>
. Java nie ma nawet aliasów typów :(colKey
jest zarówno oczywisty, jak i nieistotny: dbamy tylko o to, aby był odpowiedni jako drugi argumentdoSomethingWith
. Gdybym wyodrębnił tę pętlę do funkcji, która daje(key1, key2, value)
Iterable of -triples, najbardziej ogólną sygnaturą byłoby<K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table)
. Wewnątrz tej funkcji prawdziwy typcolKey
(Integer
, nieK2
) jest absolutnie nieistotny.View.OnClickListener listener = new View.OnClickListener()
. Nadal znasz typ, nawet jeśli programista byłby „leniwy” i skrócił go dovar listener = new View.OnClickListener
(jeśli to było możliwe). Ten rodzaj redundancji jest powszechne - nie będę ryzykować guesstimate tutaj - i usunięcie go nie wynika z myślą o przyszłych czytelników. Z każdą funkcją językową należy korzystać ostrożnie, nie kwestionuję tego.Prawdą jest, że kod jest odczytywany znacznie częściej niż jest zapisywany. Jednak czytanie wymaga również czasu, a dwa ekrany kodu są trudniejsze w nawigacji i czytaniu niż jeden ekran kodu, dlatego musimy ustalić priorytet, aby spakować najlepszy użyteczny stosunek informacji / wysiłku odczytu. Jest to ogólna zasada UX: zbyt duża ilość informacji jednocześnie przytłacza i faktycznie obniża efektywność interfejsu.
I z mojego doświadczenia wynika , że często dokładny typ nie jest (to) ważny. Na pewno czasami zagnieżdżać wyrażenia:
x + y * z
,monkey.eat(bananas.get(i))
,factory.makeCar().drive()
. Każde z nich zawiera podwyrażenia, których wynikiem jest wartość, której typ nie jest zapisany. Ale są całkowicie jasne. Nie przeszkadza nam pozostawienie tego typu bez opisu, ponieważ łatwo jest go zrozumieć z kontekstu, a jego zapisanie przyniosłoby więcej szkody niż pożytku (zaśmiecenie zrozumienia przepływu danych, zabranie cennego ekranu i krótkotrwałej pamięci).Jednym z powodów, dla których nie zagnieżdżaj wyrażeń, tak jak nie ma jutra, jest to, że linie stają się długie, a przepływ wartości staje się niejasny. Wprowadzenie zmiennej tymczasowej pomaga w tym, narzuca porządek i nadaje nazwę częściowemu wynikowi. Jednak nie wszystko, co korzysta z tych aspektów, również korzysta z tego, że określono jego typ:
Czy ma znaczenie, czy obiekt
user
jest obiektem, liczbą całkowitą, łańcuchem, czy czymś innym? W większości przypadków tak nie jest, wystarczy wiedzieć, że reprezentuje użytkownika, pochodzi z żądania HTTP i służy do pobrania nazwy wyświetlanej w prawym dolnym rogu odpowiedzi.A gdy ma to znaczenie, autor może napisać ten typ. Jest to swoboda, z której należy korzystać w sposób odpowiedzialny, ale to samo dotyczy wszystkiego, co może poprawić czytelność (nazwy zmiennych i funkcji, formatowanie, projektowanie interfejsu API, białe znaki). I rzeczywiście, konwencja w Haskell i ML (gdzie wszystko można wywnioskować bez dodatkowego wysiłku) polega na wypisaniu typów funkcji funkcji nielokalnych, a także zmiennych lokalnych i funkcji, gdy jest to właściwe. Tylko nowicjusze pozwalają wywnioskować każdy typ.
źródło
user
ma znaczenie, jeśli próbujesz rozszerzyć funkcję, ponieważ określa ona, co możesz zrobić zuser
. Jest to ważne, jeśli chcesz dodać kontrolę poprawności (na przykład z powodu luki w zabezpieczeniach) lub zapomniałeś, że musisz coś zrobić z użytkownikiem oprócz samego wyświetlania. To prawda, że tego rodzaju czytanie w celu rozszerzenia występuje rzadziej niż tylko czytanie kodu, ale są również istotną częścią naszej pracy.Myślę, że wnioskowanie na temat typu jest dość ważne i powinno być obsługiwane w każdym nowoczesnym języku. Wszyscy rozwijamy się w IDE i mogą one bardzo pomóc na wypadek, gdybyś chciał poznać wyprowadzony typ, tylko kilku z nas włamuje się
vi
. Pomyśl na przykład o gadatliwości i kodzie ceremonii w Javie.Ale możesz powiedzieć, że to w porządku, moje IDE mi pomoże, to może być ważny punkt. Jednak niektóre funkcje nie byłyby dostępne bez pomocy wnioskowania o typie, na przykład typy anonimowe w języku C #.
LINQ nie byłby tak miły, jak to jest teraz bez pomocy typu wnioskowania,
Select
na przykładTen anonimowy typ zostanie starannie wywnioskowany ze zmiennej.
Nie podoba mi się wnioskowanie o typach zwrotnych,
Scala
ponieważ myślę, że twój punkt dotyczy tutaj, powinno być dla nas jasne, co zwraca funkcja, abyśmy mogli płynniej korzystać z interfejsu APIźródło
Map<String,HashMap<String,String>>
? Jasne, jeśli nie używasz typów, pisownia ich ma niewielką korzyść.Table<User, File, String>
jest jednak bardziej pouczający, a napisanie go jest korzystne.Myślę, że odpowiedź na to pytanie jest bardzo prosta: oszczędza czytania i pisania zbędnych informacji. Zwłaszcza w językach zorientowanych obiektowo, w których po obu stronach znaku równości jest czcionka.
Co również informuje, kiedy należy lub nie należy jej używać - gdy informacje nie są zbędne.
źródło
Załóżmy, że ktoś widzi kod:
Jeśli można
someBigLongGenericType
to przypisać na podstawie typu zwracanegosomeFactoryMethod
, to jak prawdopodobne jest, aby ktoś czytający kod zauważył, jeśli typy nie pasują dokładnie, i jak łatwo ktoś, kto zauważył rozbieżność, mógł rozpoznać, czy było to zamierzone, czy nie?Umożliwiając wnioskowanie, język może zasugerować komuś, kto czyta kod, że gdy zostanie wyraźnie określony typ zmiennej, osoba ta powinna spróbować znaleźć przyczynę. To z kolei pozwala osobom, które czytają kod, lepiej skoncentrować swoje wysiłki. Jeśli natomiast w zdecydowanej większości przypadków, gdy określony jest typ, zdarza się, że jest dokładnie taki sam, jak to, co można by wywnioskować, to ktoś, kto czyta kod, może być mniej podatny na zauważenie, że jest on nieco inny .
źródło
Widzę, że jest już wiele dobrych odpowiedzi. Niektóre z nich powtórzę, ale czasami po prostu chcesz wyrazić swoje własne słowa. Skomentuję kilka przykładów z C ++, ponieważ jest to język, który znam najbardziej.
To, co jest konieczne, nigdy nie jest nierozsądne. Wnioskowanie typu jest konieczne, aby inne funkcje językowe były praktyczne. W C ++ można mieć typy niewymienne.
C ++ 11 dodał lambdy, które również są niewymienne.
Wnioskowanie typu stanowi również podstawę szablonów.
Ale twoje pytania brzmiały: „dlaczego ja, programista, chciałbym wnioskować o rodzaju moich zmiennych, gdy czytam kod? Czy nie jest szybsze, aby ktokolwiek po prostu czytał ten typ, niż zastanawiał się, jaki on jest?”.
Wnioskowanie typu usuwa nadmiarowość. Jeśli chodzi o odczyt kodu, czasem może być szybsze i łatwiejsze mieć nadmiarowe informacje w kodzie, ale nadmiarowość może przyćmić przydatne informacje . Na przykład:
Nie trzeba zbytnio zaznajomić się ze standardową biblioteką dla programisty C ++, aby stwierdzić, że i jest iteratorem,
i = v.begin()
więc deklaracja typu jawnego ma ograniczoną wartość. Swoją obecnością zasłania szczegóły, które są ważniejsze (takie jaki
wskazuje na początek wektora). Dobra odpowiedź @amon stanowi jeszcze lepszy przykład gadatliwości przesłaniającej ważne szczegóły. W przeciwieństwie do tego, wnioskowanie na podstawie typu daje większe znaczenie ważnym szczegółom.Podczas gdy czytanie kodu jest ważne, nie jest wystarczające, w pewnym momencie będziesz musiał przestać czytać i zacząć pisać nowy kod. Nadmiarowość kodu powoduje, że modyfikowanie kodu jest wolniejsze i trudniejsze. Powiedzmy, że mam następujący fragment kodu:
W przypadku, gdy muszę zmienić typ wartości wektora, aby dwukrotnie zmienić kod na:
W takim przypadku muszę zmodyfikować kod w dwóch miejscach. Porównaj z wnioskowaniem typu, gdy oryginalny kod to:
I zmodyfikowany kod:
Zauważ, że teraz muszę zmienić tylko jeden wiersz kodu. Ekstrapoluj to na duży program, a wnioskowanie o typach może propagować zmiany do typów znacznie szybciej niż w edytorze.
Nadmiarowość w kodzie stwarza możliwość błędów. Za każdym razem, gdy kod zależy od zachowania równoważności dwóch informacji, istnieje możliwość pomyłki. Na przykład występuje niespójność między tymi dwoma typami w tej instrukcji, co prawdopodobnie nie jest zamierzone:
Redundancja utrudnia rozpoznanie intencji. W niektórych przypadkach wnioskowanie typu może być łatwiejsze do odczytania i zrozumienia, ponieważ jest prostsze niż jawna specyfikacja typu. Rozważ fragment kodu:
W przypadku, gdy
sq(x)
zwraca anint
, nie jest oczywiste, czyy
jest to,int
ponieważ jest to typ zwracany,sq(x)
czy dlatego, że pasuje do używanych instrukcjiy
. Jeśli zmienię inny kod tak, abysq(x)
nie powróciłint
, z samej linii nie jest pewne, czy typy
powinien zostać zaktualizowany. Porównaj z tym samym kodem, ale używając wnioskowania typu:W tym celu intencja jest jasna,
y
musi być tego samego typu, co zwrócony przezsq(x)
. Gdy kod zmienia typ zwracanysq(x)
, typy
zmian jest dopasowywany automatycznie.W C ++ istnieje drugi powód, dla którego powyższy przykład jest prostszy w przypadku wnioskowania typu, wnioskowanie typu nie może wprowadzać niejawnej konwersji typu. Jeśli zwracany typ
sq(x)
nie jestint
, kompilator po cichu wstawia niejawną konwersję naint
. Jeśli typ zwracanysq(x)
jest typem złożonym, który definiujeoperator int()
, to ukryte wywołanie funkcji może być dowolnie złożone.źródło
typeof
jest bezużyteczne dla języka. I to jest moim zdaniem deficyt samego języka.