16

Język programowania Haskell ma pojęcia newtypes: Jeśli piszę newtype Foo = Foo (Bar), to nowy typ Foo jest stworzony, że jest izomorficzna Bar, to znaczy istnieją bijective konwersje między nimi. Właściwości tego konstruktu są następujące:języki Co programowania ma czegoś takiego Haskell za `newtype`

  • Te dwa typy są całkowicie oddzielne (tzn. Kompilator nie zezwoli na użycie go tam, gdzie oczekuje się drugiego, bez użycia jawnych konwersji).
  • Dzielą tę samą reprezentację. W szczególności funkcje konwersji mają koszt początkowy równy zero i zwracają "ten sam obiekt" na stercie.
  • Konwersja jest możliwa tylko między takimi typami i nie można jej używać w niewłaściwy sposób, tj. Bezpieczeństwo typu jest zachowane.

Jakie inne języki programowania zapewniają tę funkcję?

Jeden przykład wydaje się być wartością jednej konstrukcji w C, gdy jest używany tylko z rekordzistami/konstruktorami. Nieprawidłowymi kandydatami będą jednowartościowe-struktury w C, gdy są używane z rzutami, ponieważ rzuty nie są sprawdzane przez kompilator lub obiekty z pojedynczym elementem w Javie, ponieważ nie będą miały tej samej reprezentacji.

Powiązane pytania: Does F# have 'newtype' of Haskell? (Nie) i Does D have 'newtype'? (już nie więcej).

+1

Nie do końca rozumiem twoją argumentację, dlaczego jednowartościowe C-struktury nie kwalifikują się. Oczywiście, jeśli używasz rzutów, tracisz bezpieczeństwo typu, ale nie, jeśli używasz rekordzików i konstruktorów (tak jak robisz to również w Haskell).I każdy przyzwoity kompilator będzie wstawiał te do czegoś równoważnego rzutom, tak jak GHC robi to z 'newtype'. – leftaroundabout

+0

leftroundabout: Masz rację; zmodyfikowałem moje pytanie. –

+1

@leftaroundabout Twoje sformułowanie "każdy przyzwoity kompilator" prowadzi mnie do założenia, że ​​kompilator języka C nie jest wymagany do tego? – Ingo

Odpowiedz

11

Frege ma to, ale w przeciwieństwie do Haskella nie ma dodatkowego słowa kluczowego. Zamiast tego każdy typ produktu z jednym komponentem jest nowym typem.

przykład:

data Age = Age Int 

Ponadto, wszystkie langugaes mające nominalną pisania i pozwalają na zdefiniowanie takiego typu, w stosunku do innej powinien mieć tę funkcję. Na przykład Oberon, Modula-2 lub ADA. Po

type age = integer;  {* kindly forgive syntax errors *} 
nie można mylić wieku i innej ilości.

+0

Dzięki za wskaźnik Frege. O PASCAL: Czy to naprawdę wymaga jawnej konwersji? Jak będą wyglądać? –

+0

@JoachimBreitner Niestety, nie jestem do końca pewien, minęło trochę czasu, wiesz. Ale wiem, że można mieć coś takiego jak "typ age = 0 .. 130". Może to pomaga: http://www2.informatik.uni-halle.de/lehre/pascal/sprache/pas_cast.html, w gruncie rzeczy jest to rzutowanie typu jawnego. – Ingo

+0

W rzeczywistości okazuje się, że PASCAL nie miał nominalnego pisania. Jednak Oberon, Modula-2 i ADA to mają. Więc to zmienię. – Ingo

8

Uważam, że Scala ma value classes spełniać te warunki.

Na przykład:

case class Kelvin(k: Double) extends AnyVal 

Edycja: faktycznie, nie jestem pewien, że konwersje mają zerowe obciążenie we wszystkich przypadkach. Ten documentation opisuje niektóre przypadki, w których przydzielanie obiektów na stercie jest konieczne, więc zakładam, że w takich przypadkach wystąpiłby pewien nakład czasu podczas uzyskiwania dostępu do wartości bazowej obiektu.

+0

Czy można tworzyć nowe typy wszystkiego, czy tylko typy pierwotne? –

+0

Może przez cokolwiek, nie tylko na prymitywne typy. –

7

Go ma to:

Jeśli deklarujemy

type MyInt int 

var i int 
var j MyInt 

Potem ma typ int i j ma typ Myint. Zmienne i i j mają odrębne typy statyczne i chociaż mają ten sam podstawowy typ, nie można ich przypisać do siebie bez konwersji.

„Ten sam typ bazowy” oznacza, że ​​reprezentacja w pamięci o MyInt jest dokładnie to o int. Przekazanie MyInt do funkcji, która oczekuje, że int jest błędem podczas kompilacji. To samo dotyczy typów kompozytowych, np. po

type foo struct { x int } 
type bar struct { x int } 

nie można przekazać bar do funkcji spodziewa się foo (test).

+1

Czy możesz zilustrować błąd typu, który wynika z przypisania 'int' do' j'? Ponadto, czy masz kabel do podnoszenia funkcji na 'int' na' MyInt' (jak funkcje numeryczne)? –

+0

@DonStewart: http://play.golang.org/p/IBGEdJyB5d; i nie, nie ma automatycznego "podnoszenia". Musisz wykonać ręczne konwersje. Jednak wbudowane, takie jak '+' i '-', które nie są funkcjami, będą działać. –

+0

@larsmans: Jak przekonwertować między MyInt i int w dowolnym kierunku? Czy ta konwersja jest możliwa tylko w przypadku tych typów lub między dowolnymi typami (tj. Czy jest ona bezpieczna, nawet jeśli konwersje są używane nieprawidłowo)? –

4

Mercury to czysty język programowania logicznego, z systemem typu podobnym do Haskella.

ocena Mercury jest ścisła niż leniwy, więc nie byłoby semantyczny różnica między ekwiwalentów Mercury od newtype i data. W związku z tym każdy typ, który ma tylko jeden konstruktor z jednym argumentem, jest reprezentowany identycznie jak typ tego argumentu, ale nadal traktowany jako ten sam typ; efektywnie "nowy typ" to przejrzysta optymalizacja w Merkurym. Przykład:

:- type wrapped 
    ---> foo(int) 
    ;  bar(string). 

:- type wrapper ---> wrapper(wrapped). 

:- type synonym == wrapped. 

Przedstawienie wrapper będzie identyczna wrapped ale odrębny rodzaj, w przeciwieństwie do synonym który jest po prostu inna nazwa typu wrapped.

Mercury używa oznaczonych wskaźników w swoich reprezentacjach. Będąc surowym i mogącym mieć różne reprezentacje dla różnych typów, Mercury na ogół stara się zlikwidować boks, jeśli to możliwe. na przykład

  • Aby odwołać się do wartości z „enum-like” typu (wszystkie sygnalnych konstruktorów) nie trzeba zwrócić do dowolnego pamięci, dzięki czemu można używać warta całe słowo za bitów znaczników powiedzieć, które konstruktora go jest i inline, że w odwołaniu
  • Aby odwołać się do listy można użyć oznaczonego wskaźnika do komórki cons (zamiast wskaźnika do struktury, która sama zawiera informacje o tym, czy jest to komórka zerowa, czy komórka cons)
  • itp.

Optymalizacja "nowego typu" to tak naprawdę tylko jedna konkretna aplikacja tej ogólnej idei. Typ "opakowania" nie wymaga żadnych komórek pamięci przydzielonych powyżej tego, co już posiada typ "zapakowany". A ponieważ potrzebuje zerowych bitów, może również dopasować dowolne znaczniki w odniesieniu do typu "opakowanego". Dlatego całe odniesienie do typu "owiniętego" można wstawić w odniesieniu do typu opakowania, który kończy się nieodróżnialnym w czasie wykonywania.


Szczegóły tutaj może mieć zastosowanie jedynie do niskiego poziomu klas C kompilacji. Rtęć może również kompilować się do "wysokiego poziomu" C lub do Javy. Oczywiście w Java'u nie ma nic trudnego (choć z tego, co wiem, optymalizacja "nowego typu" wciąż obowiązuje), a ja po prostu nie znam szczegółów implementacji w klasach C wysokiego poziomu.

+1

Mówisz "to może". Czy to naprawdę? Jak wygląda konwersja i czy jest zoptymalizowany pod kątem noop? Czy skonwertowana wartość jest udostępniana z oryginalną wartością? –

+1

@JoachimBreitner Tak, naprawdę, tak konwersja to operacja "no-op" i tak, wartość konwersji jest udostępniana. Otworzyłem mówiąc, że nowością ** jest ** przejrzysta optymalizacja. "To może" pochodzi z części mojego postu wyjaśniającej, że efekt "newtype" jest częścią powiązanego zestawu optymalizacji i dlaczego można je zastosować. – Ben

Powiązane problemy