16

Il linguaggio di programmazione Haskell ha un concetto di newtypes: Se scrivo newtype Foo = Foo (Bar), quindi un nuovo tipo di Foo si crea che è isomorfo a Bar, vale a dire ci sono conversioni biunivoche tra i due. Le proprietà di questo costrutto sono:lingue Cosa programmazione hanno qualcosa del tipo `newtype` Haskell

  • I due tipi sono completamente separati (ovvero il compilatore non ti consente di usarne uno laddove l'altro è previsto, senza utilizzare le conversioni esplicite).
  • Condividono la stessa rappresentazione. In particolare, le funzioni di conversione hanno un costo di runtime pari a zero e restituiscono "lo stesso oggetto" sull'heap.
  • La conversione è possibile solo tra tali tipi e non può essere utilizzata in modo errato, ovvero la sicurezza del tipo viene mantenuta.

Quali altri linguaggi di programmazione forniscono questa funzionalità?

Un esempio sembra essere una struttura a valore singolo in C quando viene utilizzato solo con i programmi di accesso/costruttori di record. I candidati non validi sarebbero strutture a valore singolo in C se usati con i cast, poiché i cast non sono controllati dal compilatore o oggetti con un singolo membro in Java, poiché questi non condividono la stessa rappresentazione.

Domande correlate: Does F# have 'newtype' of Haskell? (No) e Does D have 'newtype'? (non più).

+1

Non capisco la tua argomentazione sul motivo per cui le strutture a C con valore singolo non sono qualificate. Ovviamente se usi lanci perdi la sicurezza del tipo, ma non se usi solo i programmi di accesso e i costruttori del record (come fai anche tu in Haskell).E qualsiasi compilatore decente li integrerà in qualcosa di equivalente ai cast, proprio come fa GHC con 'newtype'. – leftaroundabout

+0

leftroundabout: Hai un punto lì; ha modificato la mia domanda. –

+1

@leftaroundabout La tua formulazione "qualsiasi compilatore decente" mi porta ad assumere che un compilatore C non è tenuto a farlo? – Ingo

risposta

11

Frege ha questo, tuttavia, a differenza di Haskell non vi è alcuna parola chiave extra. Invece, ogni tipo di prodotto con un solo componente è un nuovo tipo.

Esempio:

data Age = Age Int 

Inoltre, tutti langugaes che hanno digitazione nominale e permettono di definire un tipo in termini di un altro dovrebbe avere questa funzione. Ad esempio Oberon, Modula-2 o ADA. Quindi dopo

type age = integer;  {* kindly forgive syntax errors *} 

non si poteva confondere un'età e qualche altra quantità.

+0

Grazie per il puntatore Frege. Informazioni su PASCAL: richiede davvero conversioni esplicite? Come sarebbero? –

+0

@JoachimBreitner Sfortunatamente, non ne sono più sicuro, è passato un po 'di tempo, lo sai. Ma so che si potrebbe anche avere qualcosa come 'type age = 0 .. 130'. Forse questo aiuta: http://www2.informatik.uni-halle.de/lehre/pascal/sprache/pas_cast.html, in sostanza, è cast di tipo esplicito. – Ingo

+0

In realtà risulta che PASCAL non avesse una digitazione nominale. Eppure Oberon, Modula-2 e ADA ce l'hanno. Quindi modifico questo. – Ingo

8

Credo che lo value classes di Scala soddisfi queste condizioni.

Ad esempio:

case class Kelvin(k: Double) extends AnyVal 

Edit: in realtà, non sono sicuro che le conversioni sono pari a zero in testa in tutti i casi. Questo documentation descrive alcuni casi in cui è necessaria l'allocazione di oggetti sull'heap, quindi presumo che in tali casi ci sarebbe un sovraccarico di runtime nell'accesso al valore sottostante dall'oggetto.

+0

Puoi creare nuovi tipi di qualcosa o solo di tipi primitivi? –

+0

Può da qualsiasi cosa, non solo tipi primitivi. –

7

Go ha questo:

Se dichiariamo

type MyInt int 

var i int 
var j MyInt 

poi ho è di tipo int e j ha digitare MyInt. Le variabili i e j hanno tipi statici distinti e, sebbene abbiano lo stesso tipo sottostante, non possono essere assegnati l'uno all'altro senza una conversione.

"Lo stesso tipo sottostante" significa che la rappresentazione in memoria di un MyInt è esattamente quella di un int. Passare un MyInt a una funzione che si aspetta un int è un errore in fase di compilazione. Lo stesso è vero per i tipi compositi, ad es. dopo

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

non è possibile passare un bar a una funzione in attesa di un foo (test).

+1

Potresti illustrare l'errore di tipo risultante dall'assegnazione di un 'int' a' j'? Inoltre, stai cablando per sollevare le funzioni su 'int' su' MyInt' (come le funzioni numeriche)? –

+0

@DonStewart: http://play.golang.org/p/IBGEdJyB5d; e no, non c'è un "sollevamento" automatico. Devi fare le conversioni manuali. Anche i built-in come '+' e '-', che non sono funzioni, funzioneranno. –

+0

@larsmans: come si converte tra MyInt e int in entrambe le direzioni? Questa conversione è possibile solo con questi tipi o tra qualsiasi tipo (vale a dire che è sicuro anche quando le conversioni vengono utilizzate in modo errato)? –

4

Mercury è un puro linguaggio di programmazione logica, con un sistema di tipi simile a quello di Haskell.

valutazione a Mercury è rigorosa, piuttosto che pigro, quindi non ci sarebbe semantica differenza tra equivalenti di newtype e data di Mercurio. Di conseguenza, qualsiasi tipo che abbia un solo costruttore con un solo argomento è rappresentato in modo identico al tipo di tale argomento, ma viene comunque considerato come lo stesso tipo; efficacemente "newtype" è un'ottimizzazione trasparente in Mercury. Esempio:

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

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

:- type synonym == wrapped. 

La rappresentazione di wrapper sarà identica a quella di wrapped ma è un tipo distinto, al contrario di synonym che è semplicemente un altro nome per il tipo wrapped.

Mercury utilizza puntatori con tag nelle sue rappresentazioni. Essendo rigoroso e avendo il permesso di avere rappresentazioni diverse per tipi diversi, Mercury generalmente cerca di eliminare il pugilato laddove possibile. per esempio.

  • fare riferimento a un valore di un tipo "enum-like" (tutti i costruttori nullaria) non è necessario per puntare a qualsiasi memoria in modo da poter utilizzare la pena di un intero parola di bit indicatori di dire quale costruttore di essa è e inline che nel riferimento
  • Per fare riferimento a un elenco è possibile utilizzare un puntatore taggato a una cella di controllo (anziché un puntatore a una struttura che contiene le informazioni sul fatto che sia nill o una cella)
  • ecc

L'ottimizzazione "newtype" è in realtà solo una particolare applicazione su quella idea generale. Il tipo "wrapper" non ha bisogno di alcuna cella di memoria allocata sopra quello che già detiene il tipo "wrapped". E poiché ha bisogno di zero tag bit, può anche adattarsi a qualsiasi tag nel riferimento al tipo "wrapped". Pertanto l'intero riferimento al tipo "wrapped" può essere sottolineato nel riferimento al tipo di wrapper, che risulta indistinguibile in fase di runtime.


I dettagli qui si possono verificare solo per il basso livello gradi C di compilazione. Mercury può anche compilare "C di alto livello" o Java. Ovviamente non c'è un po 'di fastidio in corso in Java (anche se per quanto ne so l'ottimizzazione "newtype" è ancora valida), e ho appena meno familiarità con i dettagli di implementazione nei gradi C di alto livello.

+1

Dici "può". Lo fa davvero? Inoltre, come appare la conversione, ed è ottimizzata per un noop? Il valore convertito è condiviso con il valore originale? –

+1

@JoachimBreitner Sì, lo è davvero, sì la conversione è un no-op, e sì il valore convertito è condiviso. Ho aperto dicendo che newtype ** è ** un'ottimizzazione trasparente. "Può" derivare dalla parte del mio post che spiega che l'effetto di "newtype" fa parte di un set correlato di ottimizzazioni e perché possono essere applicate. – Ben