Dlaczego zmienne potrzebują typu?

10

Więc piszemy:

Customer c = new Customer();

Dlaczego projekt nie jest taki, że piszemy:

c = new Customer();
c.CreditLimit = 1000;

Kompilator może obliczyć c punktów dla klienta i pozwolić na wywoływanie członków klienta na c?

Wiem, że możemy chcieć napisać:

IPerson c = new Customer();
IPerson e = new Employee();

aby móc pisać:

public string GetName(IPerson object)
{
    return object.Name
}

string name = GetName(c); // or GetName(e);

Ale gdybyśmy napisali:

c = new Customer();
e = new Employee();

nadal możemy napisać:

public string GetName(object)
{
    return object.Name
}    

string name = GetName(c); // or GetName(e);

Kompilator może narzekać na kod bezpośrednio powyżej, jeśli typ odwołania do obiektu c nie obsługuje właściwości Name (ponieważ może sprawdzić, które elementy są używane w argumencie / parametrze w metodzie), lub środowisko wykonawcze może narzekać.

Nawet w przypadku dynamicznego słowa kluczowego C # nadal używamy zmiennej „typ” (określonej w czasie wykonywania). Ale dlaczego zmienna w ogóle potrzebuje typu? Jestem pewien, że musi być dobry powód, ale nie mogę o tym myśleć!

strongtree
źródło
12
Właśnie dlatego istnieją dynamiczne języki, takie jak Python i Ruby (i inne). Nie ma odpowiedzi na to pytanie. Faktem jest, że niektóre języki używają deklaracji typu, a niektóre nie.
S.Lott,
2
„zmienne w tych językach w ogóle nie mają typu?” Poprawny. zmienne nie mają typu w Pythonie. Obiekty mają typ, a zmienne są po prostu odwołaniami do obiektów. Twoje pytanie jest naprawdę tylko spostrzeżeniem. W szczególności „nie wszystkie języki wymagają deklaracji zmiennych”. Nie ma odpowiedzi. Jest więc prawdopodobne, że zostanie zamknięty jako niekonstruktywny.
S.Lott,
1
Możesz przyjrzeć się Boo , które pozwala deklarować zmienne bez deklaracji typu, ale korzysta z wnioskowania o typie, aby obliczyć typy, abyś nie poświęcił poprawności i wydajności wynikających z silnego pisania.
Mason Wheeler,
2
var x = 1; - Czy miałeś na myśli liczbę 32-bitową, 64-bitową, 16-bitową? bajt? pływak? podwójnie? dziesiętny? To dotyczy tylko prymitywów.
Job
4
Możesz używać varw języku C # i bycie obeznanym z tym, gdzie chcesz zadeklarować zmienną, jest zawsze dobre.
Daniel Little

Odpowiedzi:

22

Ale dlaczego zmienna w ogóle potrzebuje typu?

  1. Może to powodować błędy, gdy niepoprawne, niepoprawnie wpisane wyrażenie jest przypisane do zmiennej. Niektóre języki mają dynamiczne pisanie , co poświęca gwarancje poprawności typu na zmienną dla rodzaju elastyczności, który wydaje się pożądany.
  2. Typy mogą umożliwiać kompilatorowi generowanie bardziej wydajnego kodu. Pisanie dynamiczne oznacza, że ​​kontrole typów muszą być wykonywane w czasie wykonywania.
Fred Foo
źródło
1
Proszę wyjaśnić opinię.
Fred Foo,
4
Pisanie dynamiczne wiąże się również z kosztem wydajności, ponieważ informacje o typie muszą być sprawdzane w czasie wykonywania.
Charles Salvia,
@CharlesSalvia: dobry punkt, dodał to do odpowiedzi.
Fred Foo
2
@sturdytree: jeśli chodzi o „jeśli nie ma typu, to nie można go źle wpisać” - jeśli chodzi o reguły językowe, to prawda. Ale zmienna może nadal mieć przypisany niewłaściwy typ z semantycznego punktu widzenia, np. Źle wpisałeś nazwę konstruktora i program nadal działa, ale nie robi tego, co chcesz.
Fred Foo,
5
@sturdytree Chociaż prawdą jest, że nie ma języków, które mają prawdziwie zmienne typowe ze sprawdzaniem typu, istnieją języki, w których wnioskowanie typu jest możliwe . To znaczy, język analizuje twoje użycie zmiennej i wydedukuje typ z twojego użycia. Jeśli występuje konflikt (np. Wykonujesz, a = new Bar()a następnie wywołujesz metodę z klasy Baz), kompilator zgłasza błąd. Języki, takie jak Haskell i OCaml, były pionierami wnioskowania o typie, ale występuje w języku C # za pomocą varsłowa kluczowego.
kwantowe
9

Masz doskonale uzasadniony punkt, istnieją języki, które nie śledzą typu zmiennej, i są nazywane „typami dynamicznymi”. Kategoria obejmuje języki takie jak JavaScript, Perl, Lisp i Python.

Zaletą, którą uzyskujemy ze statycznie typowanego języka, jest dodatkowe sprawdzanie błędów podczas kompilacji.

Załóżmy na przykład, że masz następującą metodę:

public addCustomerContact(Customer client, Employee contact) {
   ...
} 

Byłoby możliwe, jeśli masz w kodzie klienta bobi pracownika james, przez pomyłkę zadzwonić addCustomerContact(james, bob), co jest nieważne. Ale jeśli kompilator nie zna typów zmiennych, nie może ostrzec, że wykonałeś niepoprawne wywołanie, zamiast tego pojawia się błąd w czasie wykonywania ... a ponieważ języki z dynamicznym typowaniem nie sprawdzają typ parametrów przekazywanych do metod, problem ten występuje za każdym razem, gdy kod próbuje użyć właściwości jamesobiektu tylko dla klienta lub właściwości obiektu tylko dla pracownika bob. Może to potrwać długo po dodaniu pary (James, Bob) do listy kontaktów klientów.

Teraz można się zastanawiać, dlaczego nie można kompilator nadal wywnioskować typ jamesi bob, i nadal nas ostrzec? Może to czasami być możliwe, ale jeśli zmienne naprawdę nie mają żadnego typu, moglibyśmy wykonać następujące czynności:

var james;
var bob;
if (getRandomNumber() > 0.5) {
   james = new Customer();
   bob = new Employee();
} else {
   james = new Employee();
   bob = new Customer();
}

Przypisywanie dowolnej wartości do dowolnej zmiennej jest całkowicie legalne, ponieważ powiedzieliśmy, że zmienne nie mają typu. Oznacza to również, że nie zawsze możemy znać typ zmiennej, ponieważ może ona być różnego typu w zależności od różnych ścieżek wykonania.

Ogólnie rzecz biorąc, języki pisane dynamicznie są używane w językach skryptowych, w których nie ma kroku kompilacji, a więc błędy kompilacji nie istnieją, co oznacza, że ​​dodatkowe naciśnięcia klawiszy potrzebne do podania typu zmiennych nie byłyby bardzo przydatne.

Istnieją również wyraźne zalety dynamicznie pisanych języków, głównie w związku z tym, że do implementacji tego samego projektu potrzeba mniej kodu: interfejsy nie muszą być pisane, ponieważ wszystko jest „wypaczane” (dbamy tylko o to, jakie metody / właściwości ma obiekt , nie do jakiej klasy należy obiekt), zmienne nie muszą mieć jawnego typu ... z kompromisem, który dowiadujemy się o nieco mniejszej liczbie błędów, zanim zaczniemy uruchamiać nasz kod.

Theodore Murdock
źródło
Dzięki Theodore. „Ale jeśli kompilator nie zna typów zmiennych, nie może ostrzec, że wykonałeś nieprawidłowe połączenie”. Jak wspomniałeś, kompilator może znać typy obiektów, na które wskazują zmienne.
strongtree
Theodore: W twoim przykładzie james / bob powinniśmy, jako programiści, wiedzieć, w jaki sposób wykorzystaliśmy nasze zmienne (i dobre nazewnictwo pomaga), więc nie widzę z tym problemu. Kiedy mówisz „nie zawsze możemy znać typ zmiennej”, zakładam, że masz na myśli, że nie zawsze możemy znać typ obiektu, na który wskazuje zmienna, ale kompilator może to wypracować i dlatego ostrzega przed nieprawidłowymi elementami zastosowane (tzn. możemy mieć kontrolę statyczną).
strongtree
2
Podałem przykład powyżej, w którym nie można statycznie poznać typów obiektów ... w zależności od ścieżki wykonania typ obiektu przechowywany w zmiennej bez informacji o typie może być inny. Możesz mieć język taki jak C #, w którym można wywnioskować informacje o czasie kompilacji, ale, o ile wiem, nie ma języka zarówno ze statycznym sprawdzaniem typu, jak i zmiennymi naprawdę nietypowymi, prawdopodobnie złożoność obliczeniowa analizy statycznej jest również prawdopodobna świetny.
Theodore Murdock
Teodor, dzięki, zrozumiałeś poprawnie, że mówię o języku ze statycznym sprawdzaniem typu (na podstawie typu obiektu) i zmiennymi bez typu. Przykro mi, że nie ma żadnych - powiedziano mi, że Python ma zmienne bez typu, ale wygląda na to, że nie ma statycznego sprawdzania typu.
strongtree
1
-1; pisanie mocne / słabe jest prostopadłe do pisania statycznego / dynamicznego. C jest statycznie słabo wpisany; Lisp i Python są dynamicznie silnie typowane.
Fred Foo,
5

Dlatego profesjonalni programiści nie muszą się zastanawiać, czy

10 + "10"

is "1010" or 20....

Co to jest błąd, w czasie kompilacji z językiem o typie statycznym lub w czasie wykonywania z językiem o typie dynamicznym. W każdym razie rozsądne.

Tony Hopkinson
źródło
2
Tj. Perl nie jest rozsądnym językiem? :)
Fred Foo,
5
@larsmans: w rzeczywistości nie, nie jest. ale to wyłącznie opinia.
NotMe
2
@ChrisLively: Cieszę się, że to już fakt. Przyjdź do mnie w dowolnym miejscu pracy, aby przekonać moich kochających Perla kolegów;)
Fred Foo
2
10 + „10” używa operatora „+” na obiekcie typu integer i obiekcie typu string. Kompilator zgłosi błąd. Moje pytanie dotyczy rodzaju zmiennej, a nie obiektu.
strongtree
2
To jest ważne C: to wskaźnik arytmetyka.
dan04
4

Zakładając, że masz zmienną one(ustawioną na 1) i próbujesz ocenić one + one. Jeśli nie masz pojęcia o typie, 1 + 1 będzie dwuznaczny. Możesz argumentować, że 2 lub 11 mogą być poprawnymi odpowiedziami. Staje się dwuznaczny, jeśli nie podano kontekstu.

Widziałem to się zdarzyć w SQLite gdy typy baz danych zostały przypadkowo ustawiony VARCHARzamiast INTi gdy operacje zostały wykonane ludzie coraz nieoczekiwane rezultaty.

W c # jeśli kontekst podaje typ, możesz użyć varsłowa kluczowego.

var c = new Customer();
var e = new Employer();

W czasie kompilacji skompiluje c oraz ez wywnioskowanymi typami.

Stephen Quan
źródło
1
W Pythonie 1 + 1zawsze ma typ int, ale nie trzeba go deklarować. Pytanie dotyczy tego, dlaczego zmienne mają typ, a nie wartości .
Fred Foo
Przepraszam, że miałeś variablesnie widzieć, valueskiedy użyłem 1 + 1w moim przykładzie. Chyba nie było jasne.
Stephen Quan
Jednak dynamicznie pisane języki mogą sobie z tym poradzić. W Pythonie one=1; print(one+one)drukuje 2. one="1"; print(one+one)odciski 11. Przykład SQLite jest bardziej przekonujący, ale problem polega na słabym pisaniu, więc nie jest tak naprawdę istotny dla C #.
Fred Foo,
W moim sugerowanym schemacie jeden = 1 oznacza zmienną wskazującą na typ liczby całkowitej (nie sugeruję wcale żadnych typów - obiekty miałyby typ). jeden + jeden nie byłby wtedy niejednoznaczny (dodajemy dwa obiekty / wartości całkowite)
strongtree
1
Cześć @ dan04, widziałem to z ORDER BYnieumyślnym wykonaniem na VARCHARpolu. Zobacz stackoverflow.com/questions/9103313/… .
Stephen Quan
4

Zmienna nie musi mieć powiązanego typu. Języki, w których jest to prawdą, obejmują Lisp, Scheme, Erlang, Prolog, Smalltalk, Perl, Python, Ruby i inne.

Możliwe jest również, że zmienna ma typ, ale może nie trzeba pisać tego typu w programie. Nazywa się to zwykle wnioskowaniem typu. ML, Haskell i ich potomkowie mają potężne wnioskowanie o typach; niektóre inne języki mają go w mniejszych formach, na przykład autodeklaracje C ++ .

Głównym argumentem przeciwko wnioskowaniu typu jest to, że szkodzi ono czytelności. Zazwyczaj łatwiej jest zrozumieć kod, gdy typy są zapisywane.

Ryan Culpepper
źródło
1

Kiedy identyfikujesz typ reprezentowany przez twoją zmienną, wypowiadasz się na temat kilku rzeczy. Identyfikujesz wymagania dotyczące alokacji pamięci dla zmiennej i definiujesz zasady zgodności i zasięgu dla zmiennej. Zapewnia to sposób na uniknięcie nieporozumień co do twoich zamiarów dotyczących przechowywanych danych oraz zapewnia stosunkowo tani sposób identyfikacji potencjalnych problemów w kodzie w czasie kompilacji.

Jeśli zadeklarujesz następujące zmienne:

myVar      = 5;
myOtherVar = "C";

Co możesz wnioskować na temat tych zmiennych? Czy jest myVarpodpisany czy niepodpisany? Czy to 8-bit, 64-bit, czy coś pomiędzy? Czy myOtherVarString (faktycznie tablica) czy Char? Czy to ANSI czy Unicode?

Dostarczając określone typy danych, dostarczasz kompilatorowi wskazówek, w jaki sposób może zoptymalizować wymagania dotyczące pamięci dla twojej aplikacji. Niektóre języki nie przejmują się zbytnio tego rodzaju rzeczami, pozwalając na załatwienie tych spraw w czasie wykonywania, podczas gdy inne języki pozwalają na pewną liczbę dynamicznego pisania, ponieważ analizując kod, można wywnioskować typy danych.

Inną kwestią w przypadku silnie typowanych języków jest to, że oszczędza to konieczności podawania instrukcji do kompilatora za każdym razem, gdy używasz zmiennej. Czy potrafisz sobie wyobrazić, jak okropny i nieczytelny stałby się Twój kod, gdyby za każdym razem, gdy uzyskiwałeś dostęp do zmiennej, byłeś zmuszony skutecznie ją rzucić, aby poinformować kompilator, jaką to wartość? !!

S.Robins
źródło
Dobra uwaga, chociaż kompilator może po prostu użyć najbardziej wydajnego typu (np. Małej liczby całkowitej) w oparciu o wartość, widzę, że możemy chcieć, aby „C” było obiektem ciągu, a nie char, aby móc wykonywać pewne operacje . Jednak w takich przypadkach możemy po prostu podać = (ciąg) „C”. Spowoduje to utworzenie obiektu łańcuchowego i „a” (zmienna bez typu) po prostu wskazuje na to. Nie uważam tego za bardziej okropne niż ciąg a = "C";
strongtree
0

Program komputerowy to wykres węzłów procesu opisujący, co powinna zrobić „maszyna” reprezentowana przez środowisko uruchomieniowe języka (w większości przypadków wyposażone w zestawy narzędzi), w jakiej kolejności lub w jakich warunkach. Ten wykres jest reprezentowany przez plik tekstowy (lub kilka plików tekstowych) napisany w określonym języku i (częściowo lub w całości) utworzony, gdy kompilator / tłumacz interpretuje (deserializuje) ten plik. Istnieją również środowiska (UML lub narzędzia do generowania programów graficznych), w których można zbudować ten wykres i wygenerować kod źródłowy w języku docelowym.

Dlaczego to mówię? Ponieważ prowadzi to do odpowiedzi na twoje pytanie.

Tekst programu zawiera wskazówki dotyczące tego, jak komputer powinien rozwiązać rzeczywiste zadanie, zawierający zarówno etapy procesu (warunki, działania) ORAZ strukturę (jakich komponentów używasz w rozwiązaniu). Ten ostatni oznacza, że ​​dostajesz lub tworzysz niektóre wystąpienia innych komponentów, umieszczasz je w nazwanych polach (zmiennych) i używasz ich: dostęp do ich danych i usług.

Niektóre języki dają jednolite pudełka, w których ważna jest tylko etykieta, ale możesz w nich umieścić tylko wszystko, możesz nawet użyć zmiennej o nazwie „cel”, aby zapisać „Osobę” na początku, a „Samochód” na końcu ten sam algorytm. Inne wymagają stworzenia „ukształtowanych” pudełek, a więc różnych dla Osoby lub Samochodu - chociaż nadal pozwalają ci stworzyć „ogólne pudełko” (Java Object, C / C ++ void *, Objective C „id” ...) i rzuć to, jak chcesz. Wpisane języki pozwalają wyrazić strukturę w bardziej szczegółowy sposób, tworząc „kontrakty typu” dla zmiennych (chociaż można włamać się do tego ograniczenia), podczas gdy w językach niewypowiedzianych obsługiwane jest to podejście „Z pewnością będę wiedział, co włożyłem w to pole tym razem” jako domyślne i jedyne zachowanie.

Oba podejścia są realne, mają inteligencję kompilatora, wiele książek programistycznych, praktyk i ram napisanych przy użyciu ich (i innych ton książek o tych ramach) na różnych poziomach. Więc dzisiaj odpowiedź wydaje się być bardziej kwestią gustu i wiedzy rzeczywistego zespołu programistów niż właściwie uzasadnionym, zmierzonym i zweryfikowanym stwierdzeniem, czy używać typów, czy nie.

Myślę, że nie trzeba mówić, że wolę zasady od trików, zwłaszcza w przypadku długoterminowych projektów dużego zespołu (aka: „poważnych”). Powód: o ile wiem, najbardziej prawdopodobnymi przyczynami niepowodzenia / poślizgu projektu SW są: niejasne wymagania i kiepski projekt (80%! Z badań, które znam), i tylko kilka procent pozostaje do faktycznego kodowania. Wszystkie zasady i umowy wymuszają czystsze projektowanie, myślenie naprzód i wymagają, aby decyzje były podejmowane wcześniej i przez właściwe osoby. Rodzaje oznaczają zasady: mniej „swobody” i „chłodu” - więcej przygotowania, myślenia, kodowania standardów, kontrolowanej pracy zespołowej. Dla mnie brzmi to jak czynnik sukcesu, a także „dom, słodki dom”.

Moje 2 centy.

Lorand Kedves
źródło
-3

AFIAK wszystkie języki z dynamicznym pisaniem są tłumaczonymi językami. To już jest bardzo nieefektywne, dodanie nieefektywności pisania dynamicznego nie będzie dużą stratą czasu. Jednak skompilowany język nie będzie tak naprawdę odnosił się do rzeczy po nazwie, gdy jest uruchomiony. (Z wyjątkiem sporadycznego użycia odbicia .net lub podobnych - cech, które są bardzo powolne w porównaniu do języka bazowego.) Wyszukiwanie wszystkich tych nazw będzie powolne, powolne, powolne.

Loren Pechtel
źródło
3
Wiesz źle. Istnieje wiele kompilatorów dla języków dynamicznie typowanych, a niektóre z nich są dość szybkie. Przykłady obejmują Common Lisp, Scheme i Erlang.
Ryan Culpepper
3
Nie ma czegoś takiego jak „język interpretowany”. Język to abstrakcyjny zestaw reguł matematycznych. Język nie jest ani kompilowany, ani interpretowany. Po prostu jest język. Kompilacja i tłumaczenie to cechy implementacyjne, a nie język. Każdy język można zaimplementować za pomocą interpretera, a każdy język można zaimplementować za pomocą kompilatora. I prawie każdy język ma zarówno interpretowane, jak i skompilowane implementacje, np. Istnieją interpretery dla C i kompilatory dla ECMAScript, Ruby i Python.
Jörg W Mittag
-4

Języki o typie dynamicznym są często reklamowane jako „obiektowe”. Oni nie są. Mogą być zorientowane na enkapsulację, ale nigdy zorientowane obiektowo. Orientacja obiektowa dotyczy przede wszystkim typów.

„Chłopiec jedzie rowerem brata do sklepu spożywczego i kupuje od sklepikarza bochenek chleba”. Używając orientacji obiektowej, można od razu napisać zestaw klas (typów), aby opisać ten rzeczywisty scenariusz.

W języku z dynamicznym pisaniem scenariusz mógł być reprezentowany tylko w następujący sposób:

„Obiekt przesuwa obiekt swojego obiektu na obiekt i kupuje obiekt od obiektu”.

Moc orientacji obiektowej polega na tym, że jest w stanie modelować świat w sposób naturalny, dzięki czemu twórca oprogramowania może korzystać z obu stron mózgu do pisania oprogramowania i rozwiązywać problemy bardziej jako człowiek, a nie jako programista. Ta moc jest nieobecna w dynamicznie pisanych językach.

Pisanie statyczne pozwala na lepszą wydajność kodowania, możliwość ponownego użycia i konserwacji, ponieważ zintegrowane środowiska programistyczne znają typy zmiennych. Znając typy zmiennych, IDE może zapewnić automatyczne uzupełnianie, dzięki czemu programiści nie muszą odwoływać się do definicji klasy w celu zapamiętania, czy właściwość elementu została przeliterowana „backlightControl”, „backLightControl” lub „bkLightCtrl”.

Wpisywanie statyczne pozwala na automatyczne refaktoryzowanie, ponieważ IDE zna każde miejsce, w którym zmienna przechowuje instancję refaktoryzowanego obiektu.

Pisanie statyczne pozwala na większą użyteczność i łatwość konserwacji. Pisanie dynamiczne jest lepsze dla kodu jednorazowego. Załóżmy, że nowy programista wychodzi z ulicy i patrzy na istniejący fragment kodu. Jeśli kod jest wpisany statycznie, programista może, dwoma kliknięciami myszy, zbadać definicję klasy każdej ze zmiennych, wie, do czego służy klasa, wie, jakie inne metody i właściwości są dostępne. Jeśli kod jest wpisywany dynamicznie, programista musi użyć globalnego wyszukiwania, aby dowiedzieć się, co się dzieje.

Charlie Hand
źródło
2
Obawiam się, że nie mogę się z tobą nie zgodzić w pierwszej części twojego argumentu. Języki o typie dynamicznym są zwykle „obiektowe”, ale nie „klasowe”. To ważne rozróżnienie. W twoim przykładzie żyjesz w złudzeniu. Być może masz Boyklasę, ale wątpię, by mogła zrobić wszystko, co robi prawdziwy chłopiec. Jednak w twoim dynamicznym przykładzie (Obiekt jeździ ...) wiemy jedyną ważną rzecz o tym „chłopięcym” obiekcie - może on jeździć . To podstawowa filozofia języków dynamicznych. Ma + ve i -ve s. Który lubisz to twoja opinia.
Chip