W jaki sposób system typów statycznych wpływa na projekt języka opartego na prototypach?

15

Artykuł w Wikipedii na temat języków opartych na prototypach zawiera następujący akapit:

Prawie wszystkie systemy oparte na prototypach są oparte na językach interpretowanych i dynamicznie typowanych. Systemy oparte na statycznie typowanych językach są jednak technicznie wykonalne.

W jaki sposób system typów statycznych nakłada ograniczenia lub wprowadza złożoność w języku opartym na prototypach i dlaczego istnieją bardziej dynamiczne typy prototypowe?

Joe
źródło
2
+1 i ulubione: zastanawiałem się nad tym od dłuższego czasu i nie znalazłem żadnych wyjątkowo trudnych problemów z systemem typów strukturalnych . W rzeczywistości przeszkadza mi to tak bardzo, że chcę iść naprzód i próbować stworzyć statycznie oparty na prototypie język, aby zobaczyć, jakie są problemy ...
Właśnie zaczynam ten proces z tego samego powodu :)
Joe

Odpowiedzi:

6

Granica między typem podstawowym a obiektem jest rozmyta i często sztucznie wprowadzana. Na przykład w C struktura jest tylko zbiorem rekordów, tylko pochodnym typem nieobiektywnym. W C ++ struct to klasa ze wszystkimi polami publicznymi, obiekt. Mimo to C ++ jest prawie całkowicie wstecznie kompatybilny z C ... granica jest tutaj naprawdę miękka.

W przypadku programowania opartego na prototypach obiekty muszą być modyfikowalne w czasie wykonywania. MUSZĄ być pisane na maszynie, ponieważ każda zmienia się w czasie wykonywania, klasa jednego rodzaju zmienia się w inny - zmienia się jej typ.

Możesz jednak zachować podstawowe i pochodne typy niebędące obiektami jako statyczne. Ale wprowadza to dziwną rozbieżność, obiekty są pisane na miękko, obiekty inne są pisane statycznie, a między nimi należy ustalić twardą barierę. Czy powinieneś być w stanie przekształcić strukturę? Sznurek? Czy liczba powinna być klasą, typem podstawowym, czy zestawem typów podstawowych, int / float / bignum / itp.?

To jest bardziej naturalne i łatwiejsze do nauczenia się, używania i pisania, aby mieć ten jednolity, wszystkie typy są zmienne lub żadne typy nie są zmienne w czasie wykonywania. Jeśli zadeklarujesz, że tylko jeden typ (obiekt) jest mutowalny, możesz spotkać się z bólami głowy i problemami obu światów.

Typy statyczne to:

  • łatwiejsze do wdrożenia
  • szybszy / bardziej wydajny
  • bezpieczniejszy
  • łatwiejsze w utrzymaniu / dokumentowaniu dużych systemów dzięki abstrakcji.

Typ dynamiczny to:

  • szybciej pisać
  • bardziej zwięzłe
  • łatwiejszy do nauczenia się języka
  • więcej wybaczania za błędy projektowe.

Mieszając te dwa, poświęcasz dużo.

  • Wdrożenie staje się trudniejsze niż którekolwiek z poprzednich dwóch.
  • prędkość zależy od tego, czy używasz miękkich typów, czy nie ... Jeśli tak, to niska, jeśli nie, to po co w ogóle wybierać język?
  • bezpieczeństwo typu jest niedostępne dla wszystkich typów obiektów.
  • śledzenie, jak jeden typ zmienia się w inny, jest dość trudnym zadaniem. Dokumentowanie - bardzo trudne.
  • Nadal musisz wykonywać całą księgowość podstawowymi typami, co zabija zwięzłość i szybkość pisania
  • Złożoność języka jest wyższa (trudniejsza do nauczenia) niż którekolwiek z tych „specyficznych”,
  • „wybaczenie” typu dynamicznego jest zastępowane tendencją do bardzo trudnych błędów przy niedopasowaniu typów atrybutów.
SF.
źródło
1
Podaj przykład, dlaczego obiekty muszą być „zmienne” (zakładam, że masz na myśli dodawanie i usuwanie atrybutów, a nie ich zmienianie, ponieważ zwykle nie ma to związku z pisaniem).
@delnan: nie, tak naprawdę, w programowaniu opartym na prototypach możesz kopać wnętrzności obiektu, gdy widzisz dopasowanie, usunięcie, dodanie, zastąpienie, pokrycie, zarówno metod, jak i atrybutów w instancji na żywo. To jest sedno i bardzo wygodny, elastyczny zamiennik do modyfikowania obiektów za pomocą sztywnych reguł klasycznego dziedziczenia. Jeśli język używa klasy jako typu, nie można modyfikować jej struktury w locie, jeśli typ nie jest miękki.
SF.
1
Nie wydaje mi się Zgodnie z tą samą logiką można argumentować, że programowanie klasowe potrzebuje tych samych swobód, na jakie pozwalają dynamiczne języki klasowe. Nie, statyczne prototypy z systemami typu strukturalnego opisywałyby obiekty listą swoich elementów i rekurencyjnie ich typy oraz statycznie sprawdzały, czy te elementy istnieją (i mają odpowiedni typ), wymagając, aby wszystkie elementy były podane podczas tworzenia obiektu lub obecne w prototyp, a nie tylko sposób usuwania członków. Wynik nadal wydaje mi się dość prototypowy i gwarantuje, że każdy członek jest obecny przez cały czas.
@delnan: Właśnie opisałeś klasyczne dziedzictwo poprzez kompozycję. Tak, wygląda dość prototypowo i jest (bardzo osłabionym) sposobem programowania opartego na prototypach w klasycznym języku modeli dziedziczenia. Po prostu pozbawia pb.p 90% zabawy, zabijając jego największe zalety (i jednocześnie usuwając największe niebezpieczeństwa). Tak, zgodnie ze starą analogią strzelania do stóp, w pełni wyposażony pb.p pomoże Ci zestrzelić obie nogi łyżeczką do herbaty. Jeśli nie lubisz tego rodzaju mocy, lepiej trzymaj się klasycznego dziedzictwa.
SF.
1
Mylisz „dynamiczny” z „prototypami”. Te swobody, które nie łączą się dobrze z systemami typu statycznego, nie są cechami prototypów, są cechami dynamizmu. Oczywiście dodanie statycznego pisania zapobiega im, ale nie są one częścią prototypów (to IMGO głównie nie ma klas na rzecz klonowania obiektów, które mogłyby działać jako rodzice). Ten dynamizm jest prostopadły do ​​prototypów. Wszystkie popularne języki prototypowe je zawierają, ale są one niezależne od prototypów, jak wspomniano wcześniej. Rozważ ten fragment w fikcyjnym języku: pastebin.com/9pLuAu9F . Jak to nie prototypy?
3

Trudność jest dość prosta: Patrząc na obiekty jako słowniki metod lub rzeczy reagujące na komunikaty, przestrzegaj następujących zasad dotyczących typowych języków OO:

  • Wszystkie klucze / komunikaty słownika są zazwyczaj deklarowane z góry, przy użyciu statystycznie zadeklarowanych identyfikatorów.

  • Niektóre zestawy komunikatów są deklarowane z wyprzedzeniem, a obiekty są powiązane z tymi zestawami w celu ustalenia, na które komunikaty odpowiadają.

  • Relacje włączenia jednego zestawu komunikatów będących podzbiorem innego są deklarowane statycznie i wyraźnie; niezadeklarowane, ale podzbiory logiczne są nieprawidłowe.

  • Sprawdzanie typu próbuje zapewnić, że wszystkie wiadomości są wysyłane tylko do obiektów, które na nie odpowiadają.

Każdy z tych konfliktów w pewnym stopniu koliduje z systemem opartym na prototypach:

  • Nazwy wiadomości mogą być zadeklarowane z wyprzedzeniem, w postaci „atomów” lub ciągów wewnętrznych lub czegokolwiek, ale niewiele więcej; plastyczność obiektów oznacza, że ​​przypisywanie typów do metod jest niewygodne.

  • Prawdopodobnie jest to podstawowa cecha systemu opartego na prototypach, polegająca na tym, że zestawy komunikatów są definiowane przez reakcję obiektu, a nie na odwrót. Rozsądne byłoby przypisywanie aliasów do poszczególnych kombinacji w czasie kompilacji, ale zestawy komunikatów określone w czasie wykonywania muszą być możliwe.

  • Rzeczywisty wpływ dwóch powyższych trafia do domu z relacjami włączenia, w których wyraźne deklaracje są całkowicie niewykonalne. Dziedziczenie w sensie statycznego, nominalnego podtypu jest przeciwne do systemu opartego na prototypach.

To prowadzi nas do ostatniego punktu, którego tak naprawdę nie chcemy zmieniać. Nadal chcielibyśmy zapewnić, aby wiadomości były wysyłane tylko do obiektów, które na nie odpowiadają. Jednak:

  • Nie wiemy statycznie, jakie wiadomości mogą być grupowane razem.
  • Nie wiemy, które grupy są podzbiorami innych.
  • Nie wiemy, które grupy są możliwe.
  • Nie możemy nawet określić, jakie argumenty są wysyłane wraz z pojedynczą wiadomością.
  • Zasadniczo stwierdziliśmy, że nie jesteśmy w stanie sprecyzować dużo niczego w całkowicie ogólnym przypadku.

Jak więc można to obejść? Albo w jakiś sposób ogranicz całą ogólność (co jest nieprzyjemne i może szybko zniszczyć wszelkie korzyści wynikające z użycia systemu opartego na prototypach), albo spraw, aby system pisma był bardziej płynny i wyrażał ograniczenia, a nie dokładne typy .

System typów oparty na ograniczeniach szybko prowadzi do pojęcia strukturalnego podtytułu , które w bardzo luźnym sensie można uznać za statyczny odpowiednik „typowania kaczego”. Największymi przeszkodami są tutaj to, że takie systemy są znacznie bardziej skomplikowane w sprawdzaniu typu i są mniej znane (co oznacza niewiele wcześniejszej pracy do nauki).

Podsumowując: Jest to możliwe, jest to po prostu trudniejsze niż system o nominalnym typie statycznym lub system dynamiczny oparty na metadanych środowiska wykonawczego, a zatem mało osób się tym przejmuje.

CA McCann
źródło
1

Uważam, że sposobem na osiągnięcie statycznie typowanego języka opartego na prototypach byłoby oparcie go na szablonach i koncepcjach.

Pojęcia były kiedyś planowaną funkcją dla C ++ 0x. Kod ogólny w szablonach C ++ jest już de facto „statycznie zapisywany”. Ideą Pojęć jest możliwość powiedzenia pewnych rzeczy na temat wymaganych elementów i charakterystyk typów, bez wymagania lub sugerowania modelu dziedziczenia klas leżącego u podstaw tej relacji (ponieważ musiał współpracować z istniejącym kodem szablonu, który był już „statycznie zapisany” ).

W języku opartym od podstaw na szablonach i pojęciach koncepcje będą oparte na prototypach, a szablony uwolnią cię od dbania o dowolny model klasy, który może, ale nie musi być użyty do implementacji typów wartości.

Oprócz sztuczek polegających na stosowaniu kompilacji etapowej, aby język mógł być własnym metajęzykiem, te prototypowe pochodne Pojęć z konieczności byłyby niezmienne po utworzeniu. Jednak zarzut, że nie jest oparty na prototypie, to czerwony śledź. Byłby to po prostu język funkcjonalny. Przynajmniej próbowano zastosować dynamiczny język bazowy prototypów, który jest również funkcjonalny .

Dennis Ferron
źródło