Parametryczny polimorfizm w Haskell polega na tym, że wszystkie wartości typów t :: *
są równomiernie reprezentowane jako wskaźnik do obiektu wykonawczego. Tak więc ten sam kod maszynowy działa dla wszystkich wystąpień wartości polimorficznych.
Kontrastowe funkcje polimorficzne w Rust lub C++. Na przykład, funkcja identyfikacji nadal ma typ analogiczny do forall a. a -> a
, ale ponieważ wartości różnych typów a
mogą mieć różne rozmiary, kompilatory muszą generować różne kody dla każdej instatiacji. Oznacza to również, że nie możemy przekazać funkcje polimorficzne wokół w pudełkach Czas:
data Id = Id (forall a. a -> a)
ponieważ taka funkcja musiałaby działać poprawnie dla dowolnych rozmiarów obiektów. Aby ta funkcja była dostępna, wymagana jest dodatkowa infrastruktura, na przykład możemy wymagać, aby funkcja wykonawcza forall a. a -> a
pobierała dodatkowe niejawne argumenty, które przenoszą informacje o rozmiarze i konstruktorach/destruktorach wartości a
.
Teraz problem z newtype Vec = Vec (# Float#, Float# #)
jest to, że mimo Vec
ma rodzaj *
, kodu wykonawczego, który oczekuje wartości niektórych t :: *
nie może poradzić. Jest to pula przydzielona do pary, a nie wskaźnik do obiektu Haskella, a przekazanie jej do kodu spodziewającego się obiektów Haskella może spowodować błędy w postaci segmentów lub błędów.
Ogólnie rzecz biorąc (# a, b #)
nie jest koniecznie wielkością pointeru, więc nie możemy go skopiować do pól danych o rozmiarach pointeru.
Typy rodzin zwracających #
typy są niedozwolone z przyczyn pokrewnych. Rozważmy następujący:
type family Foo (a :: *) :: # where
Foo Int = Int#
Foo a = (# Int#, Int# #)
data Box = forall (a :: *). Box (Foo a)
Nasz Box
nie jest reprezentowalna wykonawcze, ponieważ Foo a
ma różne rozmiary dla różnych a
-s. Ogólnie, polimorfizm ponad #
wymagałby generowania różnych kodów dla różnych wystąpień, jak w Rust, ale to źle wpływa na regularny polimorfizm parametryczny i sprawia, że odtwarzanie wartości polimorficznych jest trudne, więc GHC nie przejmuje się tym.
(nie mówiąc jednak, że użyteczny realizacja nie mógłby zostać opracowany)
"Problem z' nowym typem Vec = Vec (# Float #, Float # #) 'jest taki, że' Vec' ma rodzaj '*' "Ale dlaczego? Dlaczego musi mieć rodzaj "*"? Kiedy piszę 'type Vec = (# Float #, Float # #)' Vec ma rodzaj '# ', więc spodziewam się, że newty będzie miał również rodzaj" # ". –
'typ Vec = ...' jest tylko synonimem. 'newtype' definiuje, no cóż, nowy typ, który różni się od typu wartości opakowanej. Zdarza się tak, że wszystkie konstrukty Haskella do definiowania nowych typów zwracają się w rodzaju "*". 'newtype's dla typów unboxed wymagałoby dużej części infrastruktury, o której wspomniałem powyżej, dla umożliwienia polimorfizmu obiektów o różnej wielkości, lub byłoby monomorficzne tylko w użyciu, co uczyniłoby je nieszczególnie lepszymi w porównaniu do używania tylko typu owiniętego . –
@ AndrásKovács Może to być wykonane z rodzaju '# ', ale np. 'id :: a -> a' nie może być wyspecjalizowane w' a ~ Vec' od 'a :: *'. To ograniczenie jest związane z faktem, że środowisko wykonawcze obsługuje dane w ramkach jednolicie (ze wskaźnikami), gdy chcesz przekazać surowe dane unboxed, więc potrzebujesz niestandardowego identyfikatora dla każdego typu bez opakowania. Nie różni się to zbytnio od niezdolności Javy do posiadania generycznych, takich jak 'T' ponieważ 'int' nie jest typem odniesienia. –
chi