Czy nadal potrzebne są określone typy?

20

Jedną rzeczą, która przyszła mi do głowy pewnego dnia, są wciąż potrzebne konkretne typy lub dziedzictwo, które nas powstrzymuje. Chodzi mi o to: czy naprawdę potrzebujemy krótkiej, int, długiej, bigint itp.

Rozumiem rozumowanie, zmienne / obiekty są przechowywane w pamięci, pamięć musi zostać przydzielona i dlatego musimy wiedzieć, jak duża może być zmienna. Ale tak naprawdę, czy współczesny język programowania nie powinien być w stanie poradzić sobie z „typami adaptacyjnymi”, tzn. Jeśli coś jest kiedykolwiek przydzielane tylko w krótkim zakresie, wykorzystuje mniej bajtów, a jeśli coś jest nagle przydzielane, bardzo duża liczba, pamięć jest przydzielana odpowiednio dla tego konkretnego przypadku.

Float, real i double są nieco trudniejsze, ponieważ typ zależy od wymaganej precyzji. Ciągi powinny jednak być w stanie zajmować mniej pamięci w wielu przypadkach (w .Net), w których używa się głównie ascii, ale ciągih zawsze zajmują podwójną pamięć z powodu kodowania Unicode.

Jednym z argumentów za konkretnymi typami może być to, że jest to część specyfikacji, tj. Na przykład zmienna nie powinna być większa niż pewna wartość, więc ustawiliśmy ją na skrót. Ale dlaczego nie zamiast tego mieć ograniczenia typu? Byłoby znacznie bardziej elastyczne i wydajne, aby móc ustawić dopuszczalne zakresy i wartości zmiennych (i właściwości).

Zdaję sobie sprawę z ogromnego problemu związanego z przebudową architektury typu, ponieważ jest ona tak ściśle zintegrowana z podstawowym sprzętem, a sprawy takie jak serializacja mogą stać się naprawdę trudne. Ale z punktu widzenia programowania powinno być świetnie, nie?

Homde
źródło
6
PHP, Ruby, Perl i inne nie wymagają podawania typów zmiennych. Środowisko to rozwiązuje.
FrustratedWithFormsDesigner
7
Ciągi znaków Unicode nie muszą zajmować dodatkowej pamięci, jeśli są używane tylko do ASCII (UTF-8).
2
Ale istnieje różnica między wariantem a typem adaptacyjnym IMO. Warianty wcale nie są wpisywane, ale są przypisywane, gdy są przypisane, natomiast typy adaptacyjne byłyby pisane, ale bardziej luźno. (i podoba mi się koncepcja ograniczeń typu)
Homde
To przypomina mi ten projekt: tom.lokhorst.eu/media/…
LennyProgrammers
4
Co z Adą? type hour is range 0 .. 23;
mouviciel

Odpowiedzi:

12

Całkowicie wierzę, że tak jest. Ograniczenia semantyczne są warte więcej niż ograniczenia wdrażania. Martwienie się rozmiarem czegoś wydaje się troską o szybkość czegoś, gdy powstaje programowanie obiektowe.

Nie zastąpił programowania krytycznego pod względem wydajności. Po prostu sprawiło, że programowanie krytyczne bez wydajności jest bardziej produktywne.

Mark Canlas
źródło
1
Sprawdź umowy kodu w .NET 4.0.
Steven Jeuris,
+1 Jeśli chodzi o przechowywanie / transmisję danych (np. Tworzenie sieci), ograniczenia są fundamentalne dla maksymalizacji wydajności protokołu / implementacji. Ponadto, jeśli są dostępne kolekcje na maszynie, można wiele zyskać. Poza tym można bezpiecznie założyć, że wydajność może zająć miejsce na drugim planie (szczególnie jeśli zmniejsza prawdopodobieństwo błędów semantycznych).
Evan Plaice,
9

Typy adaptacyjne oznaczają logikę do wykonania adaptacji, oznaczają pracę w środowisku wykonawczym, aby uruchomić tę logikę (tworzenie szablonów i czas kompilacji wymagałoby określonego typu, a wnioskowanie o typ jest szczególnym przypadkiem, w którym uzyskuje się to, co najlepsze z dwóch światów). Ta dodatkowa praca może być odpowiednia w środowiskach, w których wydajność nie jest krytyczna, a system utrzymuje odpowiedni rozmiar. W innych środowiskach tak nie jest (systemy wbudowane to takie, w których czasami trzeba użyć liczb całkowitych 32/64-bitowych dla wydajności procesora i typów całkowitych 8/16-bitowych dla optymalizacji statycznej kopii zapasowej pamięci).

Nawet języki ogólnego przeznaczenia, które obsługiwały późne wiązanie (rozwiązywanie typów w środowisku wykonawczym, takie jak VB6), obecnie mają tendencję do promowania silnego pisania (VB.NET), z powodu spadku wydajności, który pojawiał się, gdy nadużycie późnego wiązania było nadużywane, i ponieważ często kończą się brzydkim kodem, gdy typy nie są jawne ( Reference / Professional Refactoring in Visual Basic - Danijel Arsenovski ).

Matthieu
źródło
Proszę zdefiniować „automatyczne pisanie”.
@ delnan: zastąpiłem automatyczne pisanie późnym wiązaniem, co miałem na myśli :)
Matthieu
Istnieje wiele języków ogólnego przeznaczenia, które rozwiązują typy w czasie wykonywania, a Common Lisp to tylko jeden z nich. (Dla celów wydajnościowych możesz deklarować typy we wspólnej Lisp, więc możesz to robić tylko w sekcjach o kluczowym znaczeniu).
David Thornley
@David Thornley: „wymuszanie” silnego pisania mogło być zbyt silne, „promowanie” byłoby bardziej odpowiednie, odpowiednio zaktualizowałem moją odpowiedź. Język, który pozwala wybrać między dwoma rodzajami wiązania w zależności od sytuacji, jest z pewnością lepszy niż wymuszenie w taki czy inny sposób. Zwłaszcza gdy nie programujesz na niskim poziomie i koncentrujesz się na logice.
Matthieu
4

Prostota, pamięć i szybkość Kiedy deklaruję zmienną, pamięć dla tej zmiennej jest przydzielana w jednym bloku. Aby wesprzeć dynamicznie rosnącą zmienną, musiałbym dodać pojęcie nieciągłej pamięci do tej zmiennej (albo to, albo zarezerwować największy blok, który zmienna może reprezentować). Nieprzylegająca pamięć zmniejszyłaby wydajność przy przypisywaniu / pobieraniu. Przydzielenie największego możliwego byłoby marnotrawstwem w scenariuszu, w którym potrzebuję tylko bajtu, ale system rezerwuje długo.

Pomyśl o kompromisach między tablicą a wektorem (lub połączoną listą). W przypadku tablicy wyszukiwanie określonej pozycji jest prostą sprawą, aby uzyskać pozycję początkową i przesunąć wskaźnik pamięci x spacje, aby zlokalizować tę nową pozycję w pamięci. Pomyśl o int jako trochę [32] czytanie int polega na przejściu przez tę tablicę, aby uzyskać wszystkie wartości bitów.

Aby utworzyć typ liczby dynamicznej, musisz zmienić go z tablicy bitów na wektor bitów. Odczytywanie numeru dynamicznego polega na podchodzeniu do głowy, zdobyciu tego bitu, zapytaniu, gdzie jest następny bit w pamięci, przejściu do tej lokalizacji, pobraniu tego bitu itp. Dla każdego bitu w liczbie dynamicznej wykonujesz trzy operacje odczytu ( bieżący), przeczytaj (adres następnego), przenieś (następny). Wyobraź sobie, że czytasz wartości miliona liczb. To milion dodatkowych operacji. To może wydawać się nieistotne. Ale pomyśl o systemach (takich jak finanse), w których liczy się każda milisekunda.

Podjęto decyzję, że nałożenie na programistę obowiązku sprawdzenia rozmiaru i zatwierdzenia jest niewielkim kompromisem w porównaniu z obniżeniem wydajności systemu.

Michael Brown
źródło
1
Inną alternatywą jest zaimplementowanie liczb podobnych do list arraylists, w których tablica jest ponownie przydzielana, gdy liczba przerośnie obecny rozmiar. Musisz także wziąć pod uwagę przypadek, w którym użytkownik chce PRZEPŁYWU wykonać pętlę.
Michael Brown
To prawda, ale nieco uproszczenie. Możesz wymyślić bardziej wydajną strukturę macierzy, choć nie tak szybkie, jak statyczne wpisanie może być „wystarczająco szybkie” w większości przypadków. na przykład możesz zapisać informacje na blokach różnych typów, jeśli tablica nie byłaby całkowicie poszarpana, nie zajmowałoby to o wiele więcej pamięci lub wydajności. Lub tablica może poświęcić trochę pamięci, aby mieć pewnego rodzaju indeks. Tablica może nawet sam się zoptymalizować na podstawie zawartości. Nadal możesz mieć opcję wpisywania rozmiaru pamięci poprzez ograniczenie typu, jeśli potrzebujesz wydajności.
Homde
Szczerze mówiąc, nie jest to tak brutalne, jak się wydaje. Por. Moja nadchodząca odpowiedź.
Paul Nathan
3

Określone typy są wymagane w przypadku języków i projektów zorientowanych na sprzęt. Jednym z przykładów są protokoły sieciowe w sieci.

Ale stwórzmy - dla zabawy - typ varint w języku takim jak C ++. Zbuduj go z newtablicy ints.

Nie jest trudno wdrożyć dodawanie: po prostu x lub bajty razem i sprawdź wysokie bity: jeśli istnieje operacja przeniesienia, neww nowym górnym bajcie i przenieś bit. Odejmowanie następuje trywialnie w reprezentacji uzupełnienia 2. (Jest to również znane jako sumator przenoszenia tętnienia).

Mnożenie następuje podobnie; użyj iteracyjnego dodawania / przesuwania. Jak zawsze prawdziwym zwrotem w twoim ogonie jest podział [*].

Co jednak straciłeś, kiedy to się stało?

  • Deterministyczny czas. Masz funkcję syscall ( new), która może wyzwalać punkty, które niekoniecznie są kontrolowane.

  • Przestrzeń deterministyczna.

  • Matematyka półprogramowa jest powolna.

Jeśli musisz używać języka warstwy sprzętowej, a także musisz działać na wysokim (wolnym) poziomie i nie chcesz osadzać silnika skryptowego, varintma to sens. Prawdopodobnie jest gdzieś napisane.

[*] Por. Algorytmy sprzętowe matematyki dla szybszych sposobów robienia tego - zwykle sztuczka polega na operacjach równoległych.

Paul Nathan
źródło
2

To dobre pytanie. Wyjaśnia, dlaczego język taki jak Python nie potrzebuje „krótkich, całkowitych, długich, bigintu itp.”: Liczby całkowite są, no cóż, liczbami całkowitymi (w Pythonie 3 jest jeden typ liczb całkowitych) i nie mają limitu wielkości (poza oczywiście pamięć komputera).

Jeśli chodzi o Unicode, kodowanie UTF-8 (które jest częścią Unicode) używa tylko jednego znaku dla znaków ASCII, więc nie jest tak źle.

Mówiąc bardziej ogólnie, dynamiczne języki wydają się iść w kierunku, o którym wspomniałeś. Jednak ze względu na wydajność bardziej ograniczone typy są przydatne w niektórych przypadkach (np. Programy, które muszą działać szybko). Nie widzę większych zmian w dającej się przewidzieć przyszłości, ponieważ procesory organizują dane w bajtach (lub 2, 4, 8 itd.).

Eric O Lebigot
źródło
1

Na podstawie teorii języka masz rację. Typy powinny opierać się na zestawie stanów prawnych, transformacjach dostępnych dla tych stanów oraz operacjach wykonywanych na tych stanach.

Jest to jednak mniej więcej to, co oferuje programowanie OOP w typowej formie. W rzeczywistości w Javie skutecznie rozmawiasz o klasach BigIntegeri BigDecimal, które przydzielają miejsce na podstawie tego, ile potrzeba do przechowywania obiektu. (Jak zauważył FrustratedWithFormsDesigner, wiele języków skryptowych jest jeszcze dalej na tej ścieżce i nawet nie wymaga deklaracji typu i będzie przechowywać wszystko, co im dasz).

Wydajność jest jednak nadal istotna, a ponieważ zmiana typów w czasie wykonywania jest kosztowna, a ponieważ kompilatory nie mogą zagwarantować maksymalnego rozmiaru zmiennej w czasie kompilacji, nadal mamy zmienne o wielkości statycznej dla prostych typów w wielu językach.

jprete
źródło
Zdaję sobie sprawę, że jakieś dynamiczne / adaptacyjne pisanie wydaje się kosztowne i mniej wydajne niż to, co mamy teraz, i przy użyciu obecnych kompilatorów z pewnością byłyby. Ale czy jesteśmy w 100% pewni, że jeśli zbudujesz język i kompilator od podstaw, nie będziesz w stanie uczynić go, jeśli nie tak szybkim, jak jest to statycznie wpisane, przynajmniej możliwie szybko, aby był tego wart.
Homde
1
@MKO: Dlaczego nie spróbujesz i nie zobaczysz?
Anon.
1
Tak, możesz uczynić to wykonalnym szybko (ale prawdopodobnie nigdy tak szybkim jak system statyczny dla liczb). Ale część „czy warto” jest trudniejsza. Większość ludzi pracuje z danymi, których zakres mieści się wygodnie w a intlub a double, a jeśli nie, są tego świadomi, więc dynamiczne określanie wartości jest funkcją, za którą nie trzeba płacić.
jprete
Jak wszyscy programiści, oczywiście marzę o tym, by kiedyś stworzyć własny język;)
Homde
@jprete: Nie zgadzam się; większość ludzi nie zdaje sobie sprawy z możliwych dużych wyników pośrednich. Taki język może i został stworzony wystarczająco szybko do większości celów.
David Thornley,
1

To zależy od języka. W przypadku języków wyższego poziomu, takich jak Python, Ruby, Erlang i takie, masz tylko pojęcie liczb całkowitych i dziesiętnych.

Jednak dla pewnej klasy języków takie typy są bardzo ważne. Kiedy piszesz kod do odczytu i zapisu formatów binarnych, takich jak PNG, JPeg itp., Musisz dokładnie wiedzieć, ile informacji jest odczytywanych jednocześnie. To samo z pisaniem jądra systemu operacyjnego i sterowników urządzeń. Nie wszyscy to robią, aw wyższych językach używają bibliotek C do szczegółowego ciężkiego podnoszenia.

W shortdalszym ciągu jest miejsce na bardziej szczegółowe typy, ale wiele problemów programistycznych nie wymaga takiej precyzji.

Berin Loritsch
źródło
0

Niedawno utworzyłem edytor logiki drabinowej i środowisko wykonawcze i postanowiłem bardzo ograniczyć się do typów:

  • Boolean
  • Numer
  • Strunowy
  • Data i godzina

Uważam, że dzięki temu jest bardziej intuicyjny dla użytkownika. Jest to radykalne odejście od większości sterowników PLC, które mają cały „normalny” zakres typów, które można zobaczyć w języku takim jak C.

Scott Whitlock
źródło
0

Języki programowania zmierzają w tym kierunku. Weźmy na przykład łańcuchy. W starych językach musisz zadeklarować rozmiar łańcucha, jak PIC X(42)w języku COBOL, DIM A$(42)w niektórych wersjach języka BASIC lub [ VAR] CHAR(42)w języku SQL. W nowoczesnych językach masz tylko jeden dynamicznie przydzielany stringtyp i nie musisz myśleć o rozmiarze.

Liczby całkowite są jednak różne:

Chodzi mi o to: czy naprawdę potrzebujemy krótkiej, int, długiej, bigint itp.

Spójrz na Python. Służył do rozróżniania liczb całkowitych intwielkości maszyny ( long) i liczb całkowitych o dowolnym rozmiarze ( ). W wersji 3.x tej pierwszej nie ma (stara longjest nowa int) i nikt jej nie przeoczy.

Ale nadal istnieje wyspecjalizowany typ sekwencji 8-bitowych liczb całkowitych w postaci bytesi bytearray. Dlaczego nie używać tuplelub listliczb całkowitych, odpowiednio? To prawda, bytesże ma dodatkowe metody łańcuchowe, które tupletego nie robią, ale z pewnością wydajność miała z tym wiele wspólnego.

Float, real i double są nieco trudniejsze, ponieważ typ zależy od wymaganej precyzji.

Nie całkiem. Podejście „wszystko ma podwójną precyzję” jest bardzo powszechne.

dan04
źródło
1
Może typy podstawowe powinny deklarować podstawową intencję tego typu, tj. Int dla „zwykłych” liczb, podwójną dla wszystkich normalnych „miejsc po przecinku” (czy nie powinny być w stanie mieć miejsc po przecinku dla uproszczenia?) „Pieniądze” do pracy z kwotami i bajtami do pracy z danymi binarnymi. Ograniczenie typu zadeklarowane przez atrybut może pozwolić na zadeklarowanie dozwolonego zakresu, precyzji dziesiętnej, dopuszczalności zerowania, a nawet dopuszczalnych wartości. Byłoby fajnie, gdybyś mógł w ten sposób tworzyć niestandardowe i wielokrotnego użytku typy
Homde
@konrad: IMHO, powodem, dla którego liczby całkowite „bez znaku” powodują takie bóle głowy w C, jest to, że czasami są one używane do reprezentowania liczb, a czasem do reprezentowania członków otaczającego abstrakcyjnego pierścienia algebraicznego. Posiadanie osobnych typów „dzwonka” i „liczby bez znaku” może zapewnić, że kod podobny unum64 += ring32a-ring32bzawsze zapewni poprawne zachowanie, niezależnie od tego, czy domyślny typ liczby całkowitej to 16 bitów czy 64 [zauważ, że użycie +=jest kluczowe; wyrażenie takie unum64a = unum64b + (ring32a-ring32b);powinno zostać odrzucone jako dwuznaczne.]
supercat
0

Rozumiem rozumowanie, zmienne / obiekty są przechowywane w pamięci, pamięć musi zostać przydzielona i dlatego musimy wiedzieć, jak duża może być zmienna. Ale tak naprawdę, czy współczesny język programowania nie powinien być w stanie poradzić sobie z „typami adaptacyjnymi”, tzn. Jeśli coś jest kiedykolwiek przydzielane tylko w krótkim zakresie, wykorzystuje mniej bajtów, a jeśli coś jest nagle przydzielane, bardzo duża liczba, pamięć jest przydzielana odpowiednio dla tego konkretnego przypadku.

Float, real i double są nieco trudniejsze, ponieważ typ zależy od wymaganej precyzji. Ciągi powinny jednak być w stanie zajmować mniej pamięci w wielu przypadkach (w .Net), w których używa się głównie ascii, ale ciągih zawsze zajmują podwójną pamięć z powodu kodowania Unicode.

Fortran miał coś podobnego (nie wiem, czy dokładnie to masz na myśli, ponieważ naprawdę widzę dwa pytania). Na przykład, w F90 w górę, nie trzeba jawnie definiować rozmiaru typu , że tak powiem. Co jest dobre, nie tylko dlatego, że daje centralne miejsce do definiowania typów danych, ale także przenośny sposób ich definiowania. PRAWDZIWE * 4 nie jest takie samo we wszystkich implementacjach na wszystkich procesorach (a przez procesor mam na myśli procesor + kompilator), a nie przez długi czas.

selected_real_kind (p, r) zwraca wartość rodzaju prawdziwego typu danych z dokładnością dziesiętną większą niż co najmniej p cyfr i zakresem wykładnika większym co najmniej r.

Idź na przykład;

program real_kinds
integer,parameter :: p6 = selected_real_kind(6)
integer,parameter :: p10r100 = selected_real_kind(10,100) !p is precision, r is range
integer,parameter :: r400 = selected_real_kind(r=400)
real(kind=p6) :: x
real(kind=p10r100) :: y
real(kind=r400) :: z

print *, precision(x), range(x)
print *, precision(y), range(y)
print *, precision(z), range(z)
end program real_kinds

(Myślę, że jest to raczej oczywisty przykład).

Nadal nie wiem, czy poprawnie zrozumiałem twoje pytanie, i właśnie o tym myślisz.

Wieża
źródło