klasa w języku i typie OOP

9

W teorii języków programowania typ jest zbiorem wartości. Np. Typ „int” jest zbiorem wszystkich wartości całkowitych.

W językach OOP klasa jest typem, prawda?

Gdy klasa jest zdefiniowana z więcej niż jednym członkiem, np

class myclass{
    int a; 
    double b;
}

Mówiąc o klasie, mamy na myśli

  • (a,b)gdzie ajest liczbą całkowitą i bjest podwójną”, lub
  • „{ (x,y)| xis any int, yis any double}”?

Co oznacza przykład myclass?

  • (a,b)gdzie ajest liczbą całkowitą i bjest podwójną”, lub
  • obiekt, który zajmuje przestrzeń pamięci i który może (niekoniecznie tj. może być pusty) przechowywać (x,y), gdzie xjest dowolna liczba całkowita i czy yjest ona podwójna?
Tim
źródło
2
Klasa jest typem. „{(x, y) | x jest dowolną int, y jest dowolną podwójną}" " byłoby prawie poprawne, z wyjątkiem dwóch rzeczy: 1) użyłeś krotki, gdy klasa jest koncepcyjnie rekordem - odwołujesz się do jej pola według nazwy, a nie pozycji; i 2) Nie każdy rekord z polami ai bsą członkami tego typu, jak wspomina Killian Forth. Myclass jest izomorficzny w stosunku do rekordów z polami ai btypu inti double- możesz wziąć taki rekord i przekształcić go w instancją myclass.
Doval
1
W językach o silnym typie klasa jest typem. W słabo napisanych językach może, ale nie musi być typem.
shawnhcorey
1
W teorii języka programowania typ jest zbiorem wartości? Myślę, że musisz zdobyć inną książkę, innego nauczyciela lub jedno i drugie. „Zmienna” lub „stała” ma „typ” i często ma „wartość”. Istnieją typy zerowych wartości, skalary i typy wartości złożonych, w których wartość zmiennej lub stałej zawiera zmienne podrzędne / stałe podrzędne.
user1703394,
1
@ user1703394 Typ to zestaw wartości. 32-bitowa liczba całkowita jest zbiorem 2 ^ 32 różnych wartości. Jeśli wyrażenie ma wartość tego typu, wiesz, że ta wartość znajduje się w tym zestawie. Operatory matematyczne to tylko funkcje przekraczające wartości tego zbioru.
Doval
1
Byłbym również ostrożny, biorąc pod uwagę typy jako zestawy wartości. Zestawy mają relacje, które nie mają ścisłego zastosowania do typów. Do konceptualizacji typów jest to dobry model, ale psuje się, gdy zaczniesz przyglądać się bliżej - a nawet więcej, gdy wprowadzisz podtyp.
Telastyn

Odpowiedzi:

30

Ani.

Rozumiem, że pytasz, czy posiadanie tego samego zestawu typów pól wystarczy, aby zaklasyfikować je jako tę samą klasę, czy też należy je nazwać identycznie. Odpowiedź brzmi: „Nie wystarczy mieć te same typy i te same nazwy!” Klasy równoważne strukturalnie niekoniecznie są zgodne z typem.

Na przykład, jeśli masz CartesianCoordinatesi do PolarCordinatesklasy, to może zarówno mieć dwa numery w swoich dziedzinach, a może nawet mieć ten sam Numbertyp i te same nazwy, ale nadal nie jest zgodny, a instancja PolarCoordinatesnie byłoby wystąpienie CartesianCoordinates. Możliwość oddzielania typów według zamierzonego celu, a nie ich bieżącej implementacji, jest bardzo przydatną częścią pisania bezpieczniejszego, łatwiejszego w utrzymaniu kodu.

Kilian Foth
źródło
9
Należy zauważyć, że w niektórych języków jest strukturalnie równoważna jest na tyle, aby jeden typ podtypu drugiej (i często odwrotnie). To jednak jest zdecydowanie rzadkie / niepopularne.
Telastyn
7
@Tim Typedef nie tworzy typu, aliasuje nazwę używaną w odniesieniu do istniejącego typu.
Doval
1
@DevSolar On wyraźnie wspomniał o C, a poza C ++, nie znam żadnego innego języka, który używa tego słowa kluczowego.
Doval
3
@Telastyn - te języki należy zabić ogniem.
Jon Story
4
@JonStory Podtypowanie strukturalne jest przydatne na poziomie modułu; jego brak zmusza cię do przekształcenia wszystkiego interfacew Javę i C #, jeśli kiedykolwiek chcesz przeprowadzić testy jednostkowe. W końcu piszesz masę płyt kotłowych, abyś mógł zmienić konkretną klasę, z której będzie korzystał twój program, nawet jeśli nie masz zamiaru go zmieniać w czasie wykonywania.
Doval
6

Typy nie są zestawami.

Widzisz, teoria mnogości ma wiele cech, które po prostu nie dotyczą typów i odwrotnie . Na przykład obiekt ma jeden typ kanoniczny. Może to być wystąpienie kilku różnych typów, ale tylko jeden z tych typów został użyty do jego utworzenia. Teoria zbiorów nie ma pojęcia zbiorów „kanonicznych”.

Teoria zbiorów pozwala tworzyć podzestawy w locie , jeśli masz regułę opisującą, co należy do tego podzbioru. Teoria typów na ogół na to nie pozwala. Chociaż większość języków ma Numbertyp lub coś podobnego, nie ma takiego EvenNumbertypu, a jego utworzenie nie byłoby proste. To znaczy, dość łatwo jest zdefiniować sam typ, ale wszelkie istniejące, Numberktóre się zdarzają, nie zostaną magicznie przekształcone w EvenNumbers.

W rzeczywistości powiedzenie, że można „tworzyć” podzbiory, jest nieco nieuczciwe, ponieważ zestawy są zupełnie innym rodzajem zwierząt. W teorii zbiorów te podzbiory już istnieją , na wszystkie nieskończone sposoby, jak je zdefiniować. W teorii typów zwykle oczekujemy, że będziemy mieć do czynienia ze skończoną (jeśli dużą) liczbą typów w danym momencie. Jedynymi typami, o których mówi się, że istnieją, są te, które faktycznie zdefiniowaliśmy, nie każdy typ, który moglibyśmy zdefiniować.

Zestawy nie mogą zawierać się bezpośrednio lub pośrednio . Niektóre języki, takie jak Python, zapewniają typy o mniej regularnych strukturach (w Pythonie typetypem kanonicznym jest typei objectjest uważany za przykład object). Z drugiej strony większość języków nie pozwala typom zdefiniowanym przez użytkownika angażować się w tego rodzaju oszustwa.

Zestawy zwykle mogą się nakładać, ale nie są ze sobą powiązane. Jest to rzadkie w teorii typów, chociaż niektóre języki obsługują ją w postaci wielokrotnego dziedziczenia. Inne języki, takie jak Java, dopuszczają tylko ograniczoną formę tego lub całkowicie go zabraniają.

Pusty typ istnieje (nazywany jest typem dolnym ), ale większość języków go nie obsługuje lub nie uważa go za typ pierwszej klasy. „Typ zawierający wszystkie inne typy” również istnieje (nazywany jest typem górnym ) i jest szeroko obsługiwany, w przeciwieństwie do teorii mnogości.

Uwaga : Jak zauważyli wcześniej niektórzy komentatorzy (zanim wątek został przeniesiony na czat), możliwe jest modelowanie typów za pomocą teorii mnogości i innych standardowych konstrukcji matematycznych. Na przykład można modelować członkostwo typu jako relację, a nie modelować typy jako zestawy. Ale w praktyce jest to o wiele prostsze, jeśli użyjesz teorii kategorii zamiast teorii mnogości. Tak na przykład Haskell modeluje swoją teorię typów.


Pojęcie „podtypu” naprawdę różni się od pojęcia „podzbioru”. Jeśli Xjest podtypem Y, oznacza to, że możemy zastąpić wystąpienia Ywystąpieniami, Xa program nadal będzie w pewnym sensie „działał”. Jest to raczej behawioralne niż strukturalne, chociaż niektóre języki (np. Go, Rust, prawdopodobnie C) wybrały ten drugi ze względów wygody, albo dla programisty, albo dla implementacji języka.

Kevin
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Inżynier świata
4

Algebraiczne typy danych są sposobem na omówienie tego.

Istnieją trzy podstawowe sposoby łączenia typów:

  • Produkt. Właśnie o tym myślisz:

    struct IntXDouble{
      int a; 
      double b;
    }
    

    jest rodzajem produktu; jego wartości to wszystkie możliwe kombinacje (tj. krotki) jednego inti jednego double. Jeśli weźmiesz pod uwagę typy liczb jako zbiory, to liczność rodzaju produktu jest w rzeczywistości iloczynem liczności pól.

  • Suma. W językach proceduralnych jest to trochę niewygodne do wyrażania bezpośrednio (klasycznie dzieje się to za pomocą oznaczonych związków ), więc dla lepszego zrozumienia, oto typ sumy w Haskell:

    data IntOrDouble = AnInt Int
                     | ADouble Double
    

    wartości tego typu mają albo postać AnInt 345, albo ADouble 4.23, ale zawsze dotyczy tylko jednej liczby (w przeciwieństwie do typu produktu, gdzie każda wartość ma dwie liczby). Więc liczność: najpierw wyliczyć wszystkie Intwartości, każda musi być połączona z AnIntkonstruktorem. Plus wszystkie Doublewartości, każda w połączeniu z ADouble. Stąd typ sumy .

  • Potęgowanie 1 . Nie będę tutaj omawiać tego szczegółowo, ponieważ w ogóle nie ma wyraźnej korespondencji OO.

A co z zajęciami? Celowo użyłem słowa kluczowego structzamiast classdla IntXDouble. Chodzi o to, że klasa jako typ nie jest tak naprawdę charakteryzowana przez pola, są to jedynie szczegóły implementacji. Decydującym czynnikiem jest raczej to, jakie wyróżniające się wartości może mieć klasa.

Co jest istotne, choć jest wartość klasa może być Wartości każdy na jego podklasy ! Tak więc klasa jest w rzeczywistości rodzajem sumy, a nie typem produktu: jeśli Ai Boba byłyby wyprowadzone z myClass, myClassto zasadniczo byłaby sumą Ai B. Niezależnie od faktycznego wdrożenia.


1 To są funkcje (w sensie matematycznym! ); typ funkcji Int -> Doublejest reprezentowany przez wykładniczy DoubleInt. Szkoda, że ​​twój język nie ma odpowiednich funkcji ...

po lewej stronie
źródło
2
Przepraszam, ale myślę, że to bardzo słaba odpowiedź. Funkcje do mieć wyraźny analog OO, mianowicie metody (i interfejsów pojedynczej metody). Podstawowa definicja obiektu polega na tym, że ma on zarówno stan (pola / elementy danych), jak i zachowanie (metody / funkcje elementu); twoja odpowiedź ignoruje to drugie.
ruakh
@ruakh: nie. Możesz oczywiście implementować funkcje w OO, ale ogólnie metody nie są funkcjami ( ponieważ modyfikują stan itp.). Pod tym względem „funkcje” nie są też funkcjami w językach proceduralnych. Rzeczywiście, interfejsy pojedynczej metody statycznej są najbliższe typom funkcji / wykładniczym, ale miałem nadzieję uniknąć dyskusji na ten temat, ponieważ nie ma to związku z tym pytaniem.
lewo około
... co ważniejsze, moja odpowiedź dotyczy zachowania. Rzeczywiście zachowanie jest zwykle powodem, dla którego używasz dziedziczenia, a ujednolicenie różnych możliwych zachowań precyzyjnie oddaje aspekt sumowania klas OO.
lewo około
@ruakh Metoda nie może bez swojego obiektu. Najbliższym analogiem są staticmetody, z tą różnicą, że wciąż nie są one pierwszorzędnymi wartościami. Rzecz w większości języków OO polega na tym, że przyjmują obiekty jako najmniejszy element konstrukcyjny, więc jeśli chcesz czegoś mniejszego , musisz sfałszować go z obiektami, a wciąż kończy się przeciąganie wielu funkcji semantyki. Np. Porównywanie funkcji dla równości nie ma sensu, ale nadal można porównywać dwa faux-obiekty.
Doval
@Doval 1) możesz przekazywać metody wokół AFAIK, więc są to wartości pierwszej klasy; 2) sensowne jest porównywanie funkcji dla równości, ludzie JS robią to cały czas.
Den
2

Przepraszam, ale nie wiem o teorii „surowej”. Mogę tylko zapewnić praktyczne podejście. Mam nadzieję, że jest to dopuszczalne na programmers.SE; Nie znam tu etykiety.


Głównym tematem OOP jest ukrywanie informacji . To, kim dokładnie są członkowie danych klasy, nie powinno być interesujące dla jej klientów. Klient wysyła komunikaty do (metod wywoływania / funkcji członkowskich) instancji, która może modyfikować stan wewnętrzny. Chodzi o to, że elementy wewnętrzne klasy mogą ulec zmianie bez wpływu na klienta.

Następstwem tego jest to, że klasa jest odpowiedzialna za zapewnienie, że jej wewnętrzna reprezentacja pozostaje „ważna”. Załóżmy klasę, która przechowuje (uproszczony) numer telefonu w dwóch liczbach całkowitych:

    int areacode;
    int number;

Są to członkowie danych klasy. Jednak klasa prawdopodobnie będzie czymś znacznie więcej niż tylko elementami danych, a na pewno nie można jej zdefiniować jako „zestawu wszystkich możliwych wartości int x int”. Nie powinieneś mieć bezpośredniego dostępu do członków danych.

Konstrukcja instancji może odmówić jakichkolwiek liczb ujemnych. Być może konstrukcja w jakiś sposób znormalizuje areakodę, a nawet zweryfikuje liczbę całkowitą. W ten sposób skończyłbyś znacznie bliżej swojego "(a,b) where a is an int and b is a double", ponieważ z pewnością nie ma w nim żadnych dwóch int.

Ale to tak naprawdę nie ma znaczenia, jeśli chodzi o klasę. To nie jest ani rodzaj członków danych, ani zakres ich możliwych wartości, które określa klasę, to te metody , które są zdefiniowane dla niego.

Tak długo, jak metody te pozostaną takie same, implementator może zmienić typy danych na zmiennoprzecinkowe, BIGNUM, ciągi znaków, cokolwiek, i dla wszystkich praktycznych celów, nadal będzie to ta sama klasa .


Istnieją wzorce projektowe zapewniające, że takie zmiany wewnętrznej reprezentacji mogą być dokonywane bez zdawania sobie z tego sprawy przez klienta (np. Idiom pimpl w C ++, który ukrywa elementy danych za nieprzezroczystym wskaźnikiem ).

DevSolar
źródło
1
It is neither the type of the data members, nor the range of their possible values that defines the class, it's the methods that are defined for it.Członkowie danych nie definiują klasy tylko wtedy, gdy ją ukryjesz. To może być najczęstszy przypadek, ale z pewnością nie można powiedzieć, że dotyczy to wszystkich klas. Jeśli choćby jedno pole jest publiczne, jest tak samo ważne jak jego metody.
Doval
1
Chyba że kodujesz w Javie, gdzie nie masz wyboru, a nawet twoje głupie fałszywe rekordy braku zachowania muszą być classes. (Oznaczenie ich finalpomaga uzyskać punkt, ale nadal). Nadal masz jednak problem z protectedelementami członkowskimi, które można dziedziczyć, a zatem stanowią one część drugiego interfejsu API dla implementatorów podklas.
Doval
1
@Doval: Zrozumiałem, że jest to pytanie „teoretyczne” i dlatego trzymałem się z dala od rzeczywistych problemów językowych, jak to możliwe. (Tak jak ja trzymam się z dala od Javy i protectedjak to możliwe w praktyce. ;-))
DevSolar
3
Problem polega na tym, że a classjest konstrukcją zależną od języka. O ile wiem, nie ma czegoś takiego jak classteoria typów.
Doval
1
@Doval: Czy nie oznacza to, że teoria typów jako taka nie ma zastosowania do klas, ponieważ są konstrukcją wykraczającą poza zakres tej teorii?
DevSolar
2
  • Typ jest opis kategorii / zakres wartości, struktur złożonych, lub co ty. W przeciwnym razie jest to podobne do „interfejsu”. (W sensie agnostycznym języka. Nie tyle specyficzne dla języka. Na przykład w Javie intjest typem , ale nie ma związku ze interfacespecyfikacją pola publicznego / chronionego również nie jest częścią interface, ale częścią „interfejsu” lub typu ).

    Najważniejsze jest to, że jest to bardziej definicja semantyczna niż konkretna. Struktura uwzględnia tylko czynniki, o ile odsłonięte pola / zachowania i ich określone cele są zgodne. Jeśli nie masz obu, nie masz kompatybilności typu.

  • Klasa jest realizacja określonego typu. Jest to szablon, który faktycznie określa wewnętrzną strukturę, dołączone zachowanie itp.

cHao
źródło