Własny (język programowania)

Samego siebie
Logo
Paradygmat zorientowane obiektowo ( oparte na prototypach )
Zaprojektowany przez Davida Ungara , Randalla Smitha
Deweloper David Ungar, Randall Smith, Uniwersytet Stanforda , Sun Microsystems
Po raz pierwszy pojawiły się 1987 ; 36 lat temu ( 1987 )
Wersja stabilna
mandaryński 2017.1 / 24 maja 2017 ; 5 lat temu ( 2017-05-24 )
Dyscyplina pisania dynamiczny , mocny
Licencja Licencja podobna do BSD
Strona internetowa www.selflanguage.org _ _
Główne wdrożenia
Self
Influenced by
Smalltalk , APL
Influenced
NewtonScript , JavaScript , Io , Agora , Squeak , Lua , Factor , REBOL

Self to zorientowany obiektowo język programowania oparty na koncepcji prototypów . Self powstał jako dialekt języka Smalltalk , dynamicznie pisany i wykorzystujący kompilację just-in-time (JIT), a także podejście do obiektów oparte na prototypach: po raz pierwszy został użyty jako eksperymentalny system testowy do projektowania języków w latach 80. i 90. XX wieku . W 2006 roku Self był nadal rozwijany w ramach projektu Klein, który był wirtualną maszyną Self napisaną w całości w Self. Najnowsza wersja to 2017.1 wydana w maju 2017 r.

Kilka technik kompilacji just-in-time zostało pionierskich i udoskonalonych w badaniach Self, ponieważ były wymagane, aby język zorientowany obiektowo na bardzo wysokim poziomie działał nawet z połową szybkości zoptymalizowanego C. Znaczna część rozwoju Self miała miejsce w firmie Sun Mikrosystemy i opracowane przez nie techniki zostały później wdrożone w wirtualnej maszynie Javy HotSpot .

W pewnym momencie wersja Smalltalk została zaimplementowana w Self. Ponieważ był w stanie korzystać z JIT, dawało to również wyjątkowo dobrą wydajność.

Historia

Self został zaprojektowany głównie przez Davida Ungara i Randalla Smitha w 1986 roku podczas pracy w Xerox PARC . Ich celem było posunięcie naprzód najnowocześniejszych badań nad obiektowymi językami programowania, kiedy Smalltalk-80 został wypuszczony przez laboratoria i zaczął być poważnie traktowany przez przemysł. Przenieśli się na Uniwersytet Stanforda i kontynuowali pracę nad językiem, budując pierwszy działający kompilator Self w 1987 roku. W tym momencie skupiono się na próbie stworzenia całego systemu dla Self, w przeciwieństwie do samego języka.

Pierwsza publiczna wersja miała miejsce w 1990 roku, a rok później zespół przeniósł się do Sun Microsystems , gdzie kontynuował prace nad językiem. Kilka nowych wydań pojawiło się, aż w dużej mierze przeszło w stan uśpienia w 1995 roku wraz z wersją 4.0. Wersja 4.3 została wydana w 2006 roku i działała w systemach Mac OS X i Solaris . Nowa wersja z 2010 roku, wersja 4.4, została opracowana przez grupę składającą się z niektórych oryginalnych członków zespołu i niezależnych programistów i jest dostępna dla systemów Mac OS X i Linux , podobnie jak wszystkie kolejne wersje. Kontynuacja 4.5 została wydana w styczniu 2014 roku, a trzy lata później wersja 2017.1 została wydana w maju 2017 roku.

Środowisko konstrukcyjne interfejsu użytkownika Morphic zostało pierwotnie opracowane przez Randy'ego Smitha i Johna Maloneya dla języka programowania Self. Morphic został przeniesiony do innych znanych języków programowania, w tym Squeak , JavaScript , Python i Objective-C .

Self zainspirował również wiele języków opartych na swoich koncepcjach. Być może najbardziej godne uwagi były NewtonScript dla Apple Newton i JavaScript używane we wszystkich nowoczesnych przeglądarkach. Inne przykłady to Io , Lisaac i Agora . Rozproszony system obiektowy IBM Tivoli Framework, opracowany w 1990 roku, był na najniższym poziomie opartym na prototypie systemem obiektowym zainspirowanym Self.

Języki programowania oparte na prototypach

Tradycyjne języki obiektowe oparte na klasach są oparte na głęboko zakorzenionej dualności:

  1. Klasy określają podstawowe cechy i zachowania obiektów.
  2. Instancje obiektów są szczególnymi manifestacjami klasy.

Załóżmy na przykład, że obiekty klasy Pojazd mają nazwę i zdolność do wykonywania różnych czynności, takich jak jazda do pracy i dostarczanie materiałów budowlanych . Samochód Boba to konkretny obiekt (instancja) klasy Vehicle o nazwie „Samochód Boba”. Teoretycznie można wtedy wysłać wiadomość do samochodu Boba , aby dostarczył materiały budowlane .

Ten przykład pokazuje jeden z problemów związanych z tym podejściem: samochód Boba, który jest samochodem sportowym, nie jest w stanie przewozić i dostarczać materiałów budowlanych (w żadnym sensownym sensie), ale jest to zdolność, którą mają modele pojazdów . Bardziej użyteczny model wynika z zastosowania podklas do tworzenia specjalizacji pojazdu ; na przykład samochód sportowy i ciężarówka z platformą . Tylko obiekty klasy Ciężarówka z platformą muszą posiadać mechanizm dostarczania materiałów budowlanych ; samochody sportowe, które nie nadają się do tego typu prac, muszą tylko jechać szybko . Jednak ten głębszy model wymaga więcej wglądu podczas projektowania, wglądu, który może wyjść na jaw dopiero w miarę pojawiania się problemów.

Ta kwestia jest jednym z czynników motywujących prototypy . Dopóki nie można z całą pewnością przewidzieć, jakie cechy będzie miał zbiór obiektów i klas w odległej przyszłości, nie można właściwie zaprojektować hierarchii klas. Zbyt często program wymagałby w końcu dodania zachowań, a sekcje systemu musiałyby zostać przeprojektowane (lub refaktoryzowane ), aby wydzielić obiekty w inny sposób. [ potrzebne źródło ] Doświadczenie z wczesnymi językami obiektowymi, takimi jak Smalltalk wykazało, że ten rodzaj problemu pojawiał się wielokrotnie. Systemy miały tendencję do rozrastania się do pewnego stopnia, a następnie stawały się bardzo sztywne, ponieważ podstawowe klasy głęboko pod kodem programisty stawały się po prostu „złe”. Bez jakiegoś sposobu na łatwą zmianę oryginalnej klasy mogą pojawić się poważne problemy. [ potrzebne źródło ]

Dynamiczne języki, takie jak Smalltalk, pozwoliły na tego rodzaju zmiany za pomocą dobrze znanych metod w klasach; zmieniając klasę, obiekty na niej oparte zmieniłyby swoje zachowanie. Jednak takie zmiany musiały być wykonane bardzo ostrożnie, ponieważ inne obiekty oparte na tej samej klasie mogą spodziewać się tego „złego” zachowania: „złe” często zależy od kontekstu. (Jest to jedna z form problemu kruchej klasy bazowej ). Ponadto w językach takich jak C++ , gdzie podklasy mogą być kompilowane oddzielnie od nadklas, zmiana w nadklasie może w rzeczywistości zepsuć prekompilowane metody podklas. (Jest to kolejna postać problemu kruchej klasy bazowej, a także jedna postać problemu kruchego interfejsu binarnego ).

W Self i innych językach opartych na prototypach wyeliminowano dwoistość między klasami i instancjami obiektów.

Zamiast mieć „instancję” obiektu opartą na jakiejś „klasie”, w Self tworzy się kopię istniejącego obiektu i zmienia go. Tak więc samochód Boba zostałby utworzony poprzez utworzenie kopii istniejącego obiektu „Pojazd”, a następnie dodanie metody szybkiej jazdy , modelując fakt, że jest to Porsche 911 . Podstawowe obiekty, które są używane głównie do wykonywania kopii, nazywane są prototypami . Uważa się, że ta technika znacznie upraszcza dynamikę. Jeśli istniejący obiekt (lub zestaw obiektów) okaże się nieodpowiednim modelem, programista może po prostu stworzyć zmodyfikowany obiekt o poprawnym zachowaniu i użyć go zamiast tego. Kod korzystający z istniejących obiektów nie jest zmieniany.

Opis

Obiekty własne to zbiór „slotów”. Gniazda to metody akcesorów, które zwracają wartości, a umieszczenie dwukropka po nazwie gniazda ustawia wartość. Na przykład dla miejsca o nazwie „nazwa”

  moje  imię i nazwisko 

zwraca wartość w nazwie i

  mojej osoby  :  'foo' 

ustawia to.

Self, podobnie jak Smalltalk, używa bloków do kontroli przepływu i innych zadań. Metody to obiekty zawierające kod oprócz miejsc (których używają jako argumentów i wartości tymczasowych) i można je umieścić w gnieździe Self, tak jak każdy inny obiekt: na przykład liczbę. Składnia pozostaje taka sama w obu przypadkach.

Zauważ, że w Self nie ma rozróżnienia między polami i metodami: wszystko jest gniazdem. Ponieważ dostęp do slotów za pośrednictwem komunikatów stanowi większość składni w Self, wiele komunikatów jest wysyłanych do „self”, a „self” można pominąć (stąd nazwa).

Podstawowa składnia

Składnia dostępu do slotów jest podobna do tej w Smalltalk. Dostępne są trzy rodzaje komunikatów:

odbiornik nazwa_slotu
jednoargumentowy
odbiornik + argument
binarny
słowo kluczowe
odbiorca słowo kluczowe: arg1 With: arg2

Wszystkie komunikaty zwracają wyniki, więc odbiorca (jeśli jest obecny) i argumenty mogą same być wynikiem innych komunikatów. Podążanie za komunikatem po kropce oznacza, że ​​Self odrzuci zwróconą wartość. Na przykład:

  'Witaj świecie!'  wydrukować  . 

To jest własna wersja programu hello world . Składnia ' wskazuje na literalny obiekt łańcuchowy. Inne literały obejmują liczby, bloki i obiekty ogólne.

Grupowanie można wymusić za pomocą nawiasów. W przypadku braku wyraźnego grupowania, uważa się, że komunikaty jednoargumentowe mają najwyższy priorytet, następnie binarny (grupowanie od lewej do prawej), a słowa kluczowe mają najniższy. Użycie słów kluczowych do przypisania prowadziłoby do dodatkowych nawiasów, w których wyrażenia miały również słowa kluczowe, więc aby uniknąć tego Self, wymaga, aby pierwsza część selektora wiadomości słowa kluczowego zaczynała się od małej litery, a kolejne części zaczynały się od dużej litery.

  
              
                obowiązuje:  podstawa  dolna  pomiędzy:  ligatura  dolna  +  wysokość  I:  podstawa  górna  /  współczynnik  skali  . 

można jednoznacznie przeanalizować i oznacza to samo, co:

      ważne:  ((  podstawa  dolna  )  między:  ( (  ligatura  dolna  )  +  wysokość  )  I:  ( (  podstawa  górna  )  /  (  współczynnik  skali  )))  . 

W Smalltalk-80 to samo wyrażenie byłoby zapisane jako:

    
                   
                     valid  :=  self  base  bottom  pomiędzy:  self  ligature  bottom  +  self  height  i:  self  base  top  /  self  scale  factor  . 

zakładając, że base , ligatura , wysokość i skala nie były zmiennymi instancji self , ale w rzeczywistości były metodami.

Tworzenie nowych obiektów

Rozważ nieco bardziej złożony przykład:

    labelWidget  Kopiuj  etykietę:  „Witaj, świecie!”  . 

tworzy kopię obiektu „labelWidget” z wiadomością kopiowania (tym razem bez skrótu), a następnie wysyła do niego wiadomość, aby umieścić „Hello, World” w slocie o nazwie „label”. Teraz coś z tym zrobić:

     (  desktop  activeWindow  )  rysuj:  (  labelWidget  copy  label:  'Hello, World!'  )  . 

W tym przypadku najpierw wykonywane jest (desktop activeWindow) , zwracając aktywne okno z listy okien, o których wie obiekt pulpitu. Następnie (czytaj od wewnątrz do zewnątrz, od lewej do prawej) kod, który zbadaliśmy wcześniej, zwraca labelWidget. Na koniec widżet jest wysyłany do slotu rysowania aktywnego okna.

Delegacja

Teoretycznie każdy obiekt Self jest samodzielną jednostką. Self nie ma ani klas, ani meta-klas. Zmiany w określonym obiekcie nie wpływają na żaden inny, ale w niektórych przypadkach pożądane jest, aby tak się stało. Zwykle obiekt może rozumieć tylko komunikaty odpowiadające jego miejscom lokalnym, ale mając jedno lub więcej gniazd wskazujących nadrzędne , obiekt może delegować dowolny komunikat, którego sam nie rozumie, do obiektu nadrzędnego. Dowolny slot można ustawić jako wskaźnik nadrzędny, dodając gwiazdkę jako sufiks. W ten sposób Self obsługuje obowiązki, które wykorzystywałyby dziedziczenie w językach klasowych. Delegacji można również użyć do implementacji funkcji, takich jak przestrzenie nazw i zakresy leksykalne .

Załóżmy na przykład, że zdefiniowany jest obiekt o nazwie „konto bankowe”, który jest używany w prostej aplikacji księgowej. Zwykle ten obiekt byłby tworzony z metodami w środku, być może „wpłatą” i „wypłatą”, oraz wszelkimi potrzebnymi im miejscami na dane. Jest to prototyp, który jest wyjątkowy tylko w sposobie użycia, ponieważ jest to również w pełni funkcjonalne konto bankowe.

Cechy

Utworzenie klona tego obiektu dla „konta Boba” spowoduje utworzenie nowego obiektu, który zaczyna się dokładnie tak, jak prototyp. W tym przypadku skopiowaliśmy sloty wraz z metodami i wszelkimi danymi. Jednak bardziej powszechnym rozwiązaniem jest najpierw utworzenie prostszego obiektu zwanego obiektem cech , który zawiera elementy, które normalnie można by powiązać z klasą.

W tym przykładzie obiekt „konto bankowe” nie miałby metody wpłaty i wypłaty, ale miałby jako obiekt nadrzędny obiekt, który to robi. W ten sposób można wykonać wiele kopii obiektu konta bankowego, ale nadal możemy zmienić zachowanie ich wszystkich, zmieniając gniazda w tym obiekcie głównym.

Czym to się różni od tradycyjnych zajęć? Zastanów się dobrze nad znaczeniem:

   mojego obiektu  :  jakiś inny obiekt  . 

Ten fragment zmienia „klasę” myObject w czasie wykonywania, zmieniając wartość powiązaną z gniazdem „rodzic*” (gwiazdka jest częścią nazwy gniazda, ale nie odpowiednich komunikatów). W przeciwieństwie do dziedziczenia lub określania zakresu leksykalnego obiekt delegata można modyfikować w czasie wykonywania.

Dodawanie slotów

Obiekty w Self można modyfikować, aby zawierały dodatkowe miejsca. Można to zrobić za pomocą graficznego środowiska programistycznego lub prymitywnego „_AddSlots:”. Prymityw się od znaku podkreślenia. Należy unikać prymitywu _AddSlots, ponieważ jest to pozostałość po wczesnych implementacjach. Jednak pokażemy to w poniższym przykładzie, ponieważ skraca to kod.

Wcześniejszy przykład dotyczył refaktoryzacji prostej klasy o nazwie Vehicle, aby móc rozróżnić zachowanie samochodów osobowych i ciężarowych. W Self można to osiągnąć za pomocą czegoś takiego:

      _  AddSlots:  (  |  pojazd  <-  (  |  rodzic  *  = możliwe do klonowania   cechy  |  )  |  )  . 

Ponieważ odbiorca prymitywu „_AddSlots:” nie jest wskazany, jest to „ja”. W przypadku wyrażeń wpisywanych w monicie jest to obiekt o nazwie „lobby”. Argument „_AddSlots:” to obiekt, którego gniazda zostaną skopiowane do odbiornika. W tym przypadku jest to obiekt literalny z dokładnie jednym gniazdem. Nazwa gniazda to „pojazd”, a jego wartością jest inny obiekt literalny. Notacja „<-” oznacza drugie miejsce o nazwie „pojazd:”, którego można użyć do zmiany wartości pierwszego pola.

Znak „=” oznacza stałe miejsce, więc nie ma odpowiadającego mu elementu „rodzic:”. Dosłowny obiekt, który jest początkową wartością „pojazdu”, zawiera pojedyncze miejsce, dzięki czemu może zrozumieć komunikaty związane z klonowaniem. Prawdziwie pusty obiekt, oznaczony jako (| |) lub prościej jako (), nie może w ogóle odbierać żadnych komunikatów.

     pojazd  _  AddSlots:  (  |  nazwa  <-  'samochód'  |  )  . 

Tutaj odbiorcą jest poprzedni obiekt, który teraz będzie zawierał pola „nazwa” i „nazwa:” oprócz „rodzica*”.

     
         _  AddSlots:  (  |  sportsCar  <-  kopia  pojazdu  |  )  .  sportsCar  _  AddSlots:  (  |  driveToWork  =  (  '  '  jakiś  kod,  to  jest  metoda  ''  )  |  )  . 

Chociaż poprzednio „pojazd” i „samochód sportowy” były dokładnie takie same, teraz ten drugi zawiera nowe miejsce z metodą, której nie ma oryginał. Metody można umieszczać tylko w stałych gniazdach.

     
  _  AddSlots:  (  |  porsche911  <-  kopia  samochodu sportowego  |  )  .  nazwa  porsche911 :   'Bobs Porsche'  . 

Nowy obiekt „porsche911” zaczynał dokładnie tak samo, jak „sportsCar”, ale ostatnia wiadomość zmieniła wartość jego pola „nazwa”. Zauważ, że oba nadal mają dokładnie te same miejsca, mimo że jeden z nich ma inną wartość.

Środowisko

Jedną z cech Self jest to, że jest on oparty na tym samym rodzaju systemu maszyny wirtualnej , z którego korzystały wcześniejsze systemy Smalltalk. Oznacza to, że programy nie są samodzielnymi bytami, jak w językach takich jak C , ale do działania potrzebują całego środowiska pamięci. Wymaga to dostarczania aplikacji w porcjach zapisanej pamięci, zwanych migawkami lub obrazami . Wadą tego podejścia jest to, że obrazy są czasami duże i nieporęczne; jednak debugowanie obrazu jest często prostsze niż debugowanie tradycyjnych programów, ponieważ stan środowiska wykonawczego jest łatwiejszy do sprawdzenia i modyfikacji. (Różnica między programowaniem opartym na źródle i obrazie jest analogiczna do różnicy między programowaniem obiektowym opartym na klasach i prototypowym).

Ponadto środowisko jest dostosowane do szybkich i ciągłych zmian obiektów w systemie. Refaktoryzacja projektu „klasy” jest tak prosta, jak przeciągnięcie metod z istniejących przodków do nowych. Proste zadania, takie jak metody testowe, można wykonać, wykonując kopię, przeciągając metodę do kopii, a następnie zmieniając ją. W przeciwieństwie do tradycyjnych systemów, tylko zmieniony obiekt ma nowy kod i nie trzeba niczego przebudowywać, aby go przetestować. Jeśli metoda działa, można ją po prostu przeciągnąć z powrotem do przodka.

Wydajność

Własne maszyny wirtualne osiągnęły w niektórych testach wydajność około połowy szybkości zoptymalizowanego C.

Osiągnięto to dzięki technikom kompilacji just-in-time, które były pionierskie i ulepszone w badaniu Self, aby język wysokiego poziomu działał tak dobrze.

Zbieranie śmieci

Moduł wyrzucania elementów bezużytecznych dla Self używa pokoleniowego wyrzucania elementów bezużytecznych , które segreguje obiekty według wieku. Używając systemu zarządzania pamięcią do rejestrowania zapisów stron, można utrzymać barierę zapisu. Ta technika zapewnia doskonałą wydajność, chociaż po uruchomieniu przez pewien czas może wystąpić pełne wyrzucanie elementów bezużytecznych, co zajmuje dużo czasu. [ niejasne ]

optymalizacje

System czasu wykonywania selektywnie spłaszcza struktury wywołań. Daje to samo w sobie skromne przyspieszenie, ale umożliwia obszerne buforowanie informacji o typie i wiele wersji kodu dla różnych typów wywołujących. Eliminuje to potrzebę wykonywania wielu wyszukiwań metod i pozwala na wstawianie warunkowych instrukcji rozgałęzień i zakodowanych na stałe wywołań - często dając wydajność podobną do C bez utraty ogólności na poziomie języka, ale w systemie w pełni wyczyszczonym.

Zobacz też

Dalsza lektura

Linki zewnętrzne