Qual è la differenza tra un tipo e un tipo?


26

Sto imparando il linguaggio di programmazione Haskell e sto cercando di capire quale sia la differenza tra a typee a kind.

A quanto mi risulta, a kind is a type of type. Ad esempio, a ford is a type of care a car is a kind of vehicle.

È un buon modo di pensarci?

Perché, il modo in cui il mio cervello è attualmente collegato, a ford is a **type** of car, ma anche un car is a **type** of vehiclepo 'allo stesso tempo a car is a **kind** of vehicle. Cioè i termini typee kindsono intercambiabili.

Qualcuno potrebbe far luce su questo?


6
Sono appena arrivato qui dal post su Stack Overflow che ha portato a questa discussione. Non sono sicuro di essere qualificato per rispondere in dettaglio - ma sei decisamente troppo letterale sui termini "tipo" e "tipo", nel tentativo di metterli in relazione con il loro significato in inglese (dove in effetti sono sinonimi ). Dovresti trattarli come termini tecnici. "Tipo" è ben compreso da tutti i programmatori, suppongo, poiché il concetto è vitale per ogni lingua, anche per quelli scarsamente digitati come Javascript, "Tipo" è un termine tecnico usato in Haskell per "tipo di un tipo". Questo è davvero tutto ciò che c'è da fare.
Robin Zigmond,

2
@RobinZigmond: hai ragione nel dire che si tratta di termini tecnici, ma sono usati più ampiamente rispetto a Haskell. Forse backlink alla discussione Stack Overflow che ha sollevato questa domanda?
Andrej Bauer,

@AndrejBauer Non ho mai detto che non fossero usati al di fuori di Haskell, sicuramente il "tipo" è usato essenzialmente in tutte le lingue, come ho detto. In realtà non ho mai incontrato "gentile" fuori da Haskell, ma poi Haskell è l'unico linguaggio funzionale che conosco, e sono stato attento a non dire che il termine non è usato altrove, solo che è usato in quel modo in Haskell. (E il link, come da lei richiesto, è qui )
Robin Zigmond,

Anche le lingue della famiglia ML hanno tipi, ad esempio ML standard e OCaml. Non sono stati esplicitamente esposti con quel nome, credo. Si manifestano come firme e i loro elementi sono chiamati strutture .
Andrej Bauer,

1
Un'analogia inglese più accurata è che Ford è un tipo di auto e l'auto è un tipo di veicolo, ma sia i tipi di auto che i tipi di veicoli sono dello stesso tipo: nomi. Considerando che il rosso è un tipo di colore dell'auto e RPM è un tipo di metriche di prestazione dell'auto ed entrambi sono dello stesso tipo: aggettivi.
Slebetman,

Risposte:


32

Qui, "valori", "tipi" e "tipi" hanno significati formali, quindi considerando il loro uso inglese comune o analogie con la classificazione delle automobili ti porterà solo finora.

La mia risposta riguarda specificamente i significati formali di questi termini nel contesto di Haskell; questi significati si basano (sebbene non siano realmente identici a) i significati usati nella "teoria dei tipi" matematica / CS. Quindi, questa non sarà un'ottima risposta "informatica", ma dovrebbe servire come una risposta Haskell piuttosto buona.

In Haskell (e in altre lingue), risulta utile assegnare un tipo a un'espressione di programma che descriva la classe di valori che può avere l'espressione. Presumo qui che tu abbia visto abbastanza esempi per capire perché sarebbe utile sapere che nell'espressione sqrt (a**2 + b**2), le variabili ae bsaranno sempre valori di tipo Doublee non, diciamo, Stringe Boolrispettivamente. Fondamentalmente, avere tipi ci aiuta a scrivere espressioni / programmi che funzioneranno correttamente su una vasta gamma di valori .

Ora, qualcosa che potresti non aver capito è che i tipi di Haskell, come quelli che compaiono nelle firme dei tipi:

fmap :: Functor f => (a -> b) -> f a -> f b

sono in realtà scritti da loro stessi in una sublingua Haskell a livello di tipo. Il testo del programma Functor f => (a -> b) -> f a -> f bè - letteralmente - un'espressione di tipo scritta in questo sublanguage. La sublanguage comprende operatori (ad esempio, ->è un diritto operatore infisso associativa in questa lingua), variabili (ad esempio f, ae b), e "applicazione" di un'espressione tipo ad un altro (ad esempio, f aviene fapplicato a a).

Ho già parlato di come sia stato utile in molti linguaggi assegnare tipi alle espressioni di programma per descrivere le classi di valori di espressione? Bene, in questa sublanguage a livello di tipo, le espressioni valutano i tipi (anziché i valori ) e finisce per essere utile assegnare i tipi alle espressioni di tipo per descrivere le classi di tipi che sono autorizzati a rappresentare. Fondamentalmente, avere tipi ci aiuta a scrivere espressioni di tipo che funzioneranno correttamente su una vasta gamma di tipi .

Quindi, i valori sono ai tipi come i tipi ai tipi , ei tipi ci aiutano a scrivere programmi di livello- valore mentre i tipi ci aiutano a scrivere programmi di livello- tipo .

Che aspetto hanno questi tipi ? Bene, considera la firma del tipo:

id :: a -> a

Se l'espressione del tipo a -> adeve essere valida, che tipo di tipi dovremmo consentire aalla variabile ? Bene, le espressioni di tipo:

Int -> Int
Bool -> Bool

sembrano validi, quindi i tipi Int e Boolsono ovviamente del tipo giusto . Ma tipi ancora più complicati come:

[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]

sembra valido. Infatti, dal momento che dovremmo essere in grado di richiamare idfunzioni, anche:

(a -> a) -> (a -> a)

sembra a posto. Così, Int, Bool, [Double], Maybe [(Double,Int)], e a -> asembrano tutti i tipi del diritto tipo .

In altre parole, sembra che ci sia un solo tipo , chiamiamolo *un jolly Unix, e ogni tipo ha lo stesso tipo * , fine della storia.

Destra?

Bene, non proprio. Si scopre che Maybe, da sola, è valida un'espressione di tipo quanto Maybe Int(allo stesso modo sqrt, tutta da sola, è altrettanto valida un'espressione di valore sqrt 25). Tuttavia , l'espressione di tipo seguente non è valida:

Maybe -> Maybe

Perché, sebbene Maybesia un'espressione di tipo, non rappresenta il tipo di tipo che può avere valori. Quindi, è così che dovremmo definire *: è il tipo di tipi che hanno valori; include tipi "completi" come Doubleo Maybe [(Double,Int)]ma esclude tipi incompleti e senza valore come Either String. Per semplicità, chiamerò questi tipi completi di tipi *"tipi concreti", sebbene questa terminologia non sia universale, e "tipi concreti" potrebbe significare qualcosa di molto diverso, per esempio, un programmatore C ++.

Ora, l'espressione tipo a -> a, a condizione che il tipo aha tipo * (il tipo di tipi concreti), il risultato dell'espressione tipo a -> aavrà anche avere tipo * (ad esempio, il tipo di tipi concreti).

Quindi, che tipo di tipo è Maybe? Bene, Maybepuò essere applicato a un tipo di calcestruzzo per produrre un altro tipo di calcestruzzo. Quindi, Maybesembra un po 'come una funzione a livello di tipo che accetta un tipo di tipo * e restituisce un tipo di tipo * . Se avessimo una funzione a livello di valore che assumesse un valore di tipo Int e restituisse un valore di tipo Int , gli daremmo una firma di tipoInt -> Int , quindi per analogia dovremmo dare Maybeuna firma di tipo* -> * . GHCi concorda:

> :kind Maybe
Maybe :: * -> *

Tornando a:

fmap :: Functor f => (a -> b) -> f a -> f b

In questo tipo di firma, la variabile fha tipo * -> *e variabili ae bha tipo *; l'operatore incorporato ->ha tipo * -> * -> *(prende un tipo di tipo *a sinistra e uno a destra e restituisce un tipo anche di tipo *). Da questo e le regole di tipo deduzione, si può dedurre che a -> bè un tipo valido con il genere *, f ae f bsono anche tipi validi con il genere *, e (a -> b) -> f a -> f bè valido tipo di genere *.

In altre parole, il compilatore può "controllare il tipo" nell'espressione del tipo (a -> b) -> f a -> f bper verificare che sia valido per le variabili del tipo giusto allo stesso modo in cui "controlla il tipo" sqrt (a**2 + b**2)per verificare che sia valido per le variabili del tipo giusto.

La ragione per usare termini separati per "tipi" rispetto a "tipi" (cioè, non parlare dei "tipi di tipi") è principalmente solo per evitare confusione. I tipi sopra sembrano molto diversi dai tipi e, almeno all'inizio, sembrano comportarsi in modo abbastanza diverso. (Ad esempio, ci vuole del tempo per avvolgere la testa intorno all'idea che ogni tipo "normale" ha lo stesso tipo *e il tipo di non lo a -> bè .)** -> *

Alcuni di questi sono anche storici. Con l'evoluzione di GHC Haskell, le distinzioni tra valori, tipi e tipi hanno iniziato a confondersi. In questi giorni, i valori possono essere "promossi" in tipi e tipi e tipi sono davvero la stessa cosa. Quindi, nella moderna Haskell, i valori hanno entrambi tipi e tipi ARE (quasi), e i tipi di tipi sono solo più tipi.

@ user21820 ha chiesto una spiegazione aggiuntiva di "tipi e tipi sono davvero la stessa cosa". Per essere un po 'più chiari, nel moderno GHC Haskell (dalla versione 8.0.1, credo), i tipi e i tipi sono trattati in modo uniforme nella maggior parte del codice del compilatore. Il compilatore compie alcuni sforzi nei messaggi di errore per distinguere tra "tipi" e "tipi", a seconda che si stia lamentando rispettivamente del tipo di un valore o del tipo di un tipo.

Inoltre, se non sono abilitate estensioni, sono facilmente distinguibili nella lingua di superficie. Ad esempio, i tipi (di valori) hanno una rappresentazione nella sintassi (ad esempio, nelle firme dei tipi), ma i tipi (di tipi) sono - penso - completamente impliciti e non c'è sintassi esplicita in cui compaiono.

Ma se si attivano le estensioni appropriate, la distinzione tra tipi e tipi scompare in gran parte. Per esempio:

{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
  Bar :: Bool -> * -> Foo

Qui, Barè (sia un valore che) un tipo. Come tipo, il suo tipo è Bool -> * -> Foo, che è una funzione a livello di tipo che accetta un tipo di tipo Bool(che è un tipo, ma anche un tipo) e un tipo di tipo *e produce un tipo di tipo Foo. Così:

type MyBar = Bar True Int

verifiche del tipo corretto.

Come spiega @AndrejBauer nella sua risposta, l'incapacità di distinguere tra tipi e tipi non è sicura - avere un tipo / tipo il *cui tipo / tipo è esso stesso (come nel caso della moderna Haskell) porta a paradossi. Tuttavia, il sistema di tipi di Haskell è già pieno di paradossi a causa della non terminazione, quindi non è considerato un grosso problema.


Se "tipi e tipi sono davvero la stessa cosa", allora il tipo di typeè solo typese stesso, e non ce ne sarebbe affatto bisogno kind. Quindi qual è esattamente la distinzione?
user21820

1
@ user21820, ho aggiunto una nota alla fine che potrebbe risolvere questo problema. Risposta breve: non c'è davvero distinzione nel moderno GHC Haskell .
KA Buhr,

1
Questa è un'ottima risposta, grazie mille per la condivisione. È ben scritto e introduce concetti gradualmente - come qualcuno che non ha scritto Haskell per alcuni anni, questo è molto apprezzato!
Ultrafez,

@KABuhr: Grazie per quel po 'aggiunto!
user21820

19

type:kind=set:class.

  • Bool è un tipo
  • Type è un tipo perché i suoi elementi sono tipi
  • Bool -> Int è un tipo
  • Bool -> Type è un tipo perché i suoi elementi sono funzioni che restituiscono tipi
  • Bool * Int è un tipo
  • Bool * Type è un tipo perché i suoi elementi sono coppie con un componente un tipo

U0U1U2U0BoolNatNatNatU1U0BoolU0U0U0Un+1UnUn×

U0U1U0U1U0**U_1


2
Non credo (GHC) Haskell abbia alcun concetto di universi. Type :: Typeè un assioma. La distinzione tra "tipo" e "tipo" è interamente nel linguaggio umano, in quel caso. Trueha un tipo, Boole Boolha un tipo Type, che a sua volta ha tipo Type. A volte chiamiamo un tipo un tipo, per sottolineare che è il tipo di entità a livello di tipo, ma, in Haskell, è ancora solo un tipo. In un sistema in cui esistono universi, come Coq, allora "tipo" può riferirsi a un universo e "tipo" a un altro, ma di solito vogliamo infinitamente molti universi.
HTNW,

1
La distinzione non è solo "linguaggio umano", è una distinzione formale nel sistema di tipi sottostante. È del tutto possibile avere entrambi Type :: Typee una distinzione tra tipi e tipi. Inoltre, quale codice mostra Type :: Typein Haskell?
Andrej Bauer,

1
Dovrei anche dire che *in Haskell c'è un universo di sorta. Semplicemente non lo chiamano così.
Andrej Bauer,

3
@AndrejBauer Typeda Data.Kindse *dovrebbe essere sinonimi. Inizialmente avevamo solo *come primitivo, mentre oggi è internamente definito come GHC.Types.Typenel modulo interno GHC.Types, a sua volta definito come type Type = TYPE LiftedRep. Penso che TYPEsia il vero primitivo, fornendo una famiglia di tipi (tipi sollevati, tipi senza scatola, ...). La maggior parte della complessità "non elegante" qui è di supportare alcune ottimizzazioni di basso livello, e non per ragioni teoriche di tipo reale.
Chi

1
Proverò a riassumere. Se vè un valore, allora ha un tipo: v :: T. Se Tè un tipo, allora ha un tipo: T :: K. Il tipo di tipo si chiama tipo. I tipi che sembrano TYPE reppossono essere chiamati ordinamenti, anche se la parola non è comune. Iff T :: TYPE repè Tautorizzato a comparire sull'RHS di a ::. La parola "gentile" ha sfumature: Kin T :: Kè una specie, ma non in v :: K, sebbene sia la stessa K. Potremmo definire " Kè un tipo se il suo tipo è un tipo" aka "i tipi sono sulla RHS di ::", ma questo non cattura correttamente l'utilizzo. Pertanto la mia posizione di "distinzione umana".
HTNW,

5

Un valore è come il Ford Mustang rosso specifico del 2011 con 19.206 miglia su di esso che hai seduto sul tuo vialetto.

Quel valore specifico, in modo informale, potrebbe avere molti tipi : è una Mustang, è una Ford, è un'auto ed è un veicolo, tra molti altri tipi che potresti inventare (il tipo di "cose appartenendo a te ", o il tipo di" cose che sono rosse ", o ...).

(In Haskell, per un'approssimazione del primo ordine (i GADT rompono questa proprietà e la magia attorno ai numeri letterali e l'estensione OverloadedStrings la oscurano un po '), i valori hanno un tipo principale invece della pletora di "tipi" informali che puoi dare al tuo' stang. 42è, ai fini di questa spiegazione, un Int; non esiste alcun tipo in Haskell per "numeri" o "numeri interi" - o piuttosto, potresti crearne uno, ma sarebbe un tipo disgiunto da Int.)

Ora, "Mustang" può essere un sottotipo di "auto": ogni valore che è un Mustang è anche un'auto. Ma il tipo - o, per usare la terminologia di Haskell, il tipo di "Mustang" non è "macchina". "Mustang" il tipo non è una cosa che puoi parcheggiare nel tuo vialetto o guidare. "Mustang" è un sostantivo, una categoria o solo un tipo. Questi sono, informalmente, i tipi di "Mustang".

(Ancora una volta, Haskell riconosce solo un tipo per ogni cosa a livello di tipo. Quindi Intha tipo *, e nessun altro tipo. MaybeHa tipo * -> *, e nessun altro tipo. Ma l'intuizione dovrebbe ancora valere: 42è un Int, e puoi fare Inty cose con esso come l'aggiunta e la sottrazione. di per Intsé non è un Int; non esiste un numero come Int + Int. Puoi sentire in modo informale la gente dire che Intè una Num, con cui intendono dire che esiste un'istanza della Numclasse di tipo per il tipo: Intquesta non è la stessa cosa come dire che Intha genere Num . Intha "tipo" tipo, che in Haskell si scrive *.)

Quindi ogni "tipo" informale non è solo un nome o una categoria? Tutti i tipi hanno lo stesso tipo? Perché parlare di tipi se sono così noiosi?

È qui che l'analogia inglese diventerà un po 'rocciosa, ma abbiate pazienza con me: fingete che la parola "proprietario" in inglese non avesse alcun senso in isolamento, senza una descrizione della proprietà. Fai finta che se qualcuno ti definisse un "proprietario", ciò non avrebbe alcun senso per te; ma se qualcuno ti definisse un "proprietario dell'auto", potresti capire cosa significasse.

"Proprietario" non ha lo stesso tipo di "automobile", perché puoi parlare di un'auto, ma non puoi parlare di un proprietario in questa versione inventata dell'inglese. Puoi solo parlare di un "proprietario dell'auto". "Proprietario" crea qualcosa del tipo "sostantivo" solo quando viene applicato a qualcosa che ha già un "nome" gentile, come "macchina". Vorremmo dire che il tipo di "proprietario" è "sostantivo -> nome". "Proprietario" è come una funzione che prende un nome e produce da esso un nome diverso; ma non è un nome stesso.

Si noti che "proprietario dell'auto" non è un sottotipo di "auto"! Non è una funzione che accetta o restituisce auto! È solo un tipo completamente separato da "auto". Descrive i valori con due braccia e due gambe che a un certo punto avevano una certa quantità di denaro e li portavano a un concessionario. Non descrive i valori che hanno quattro ruote e un lavoro di verniciatura. Inoltre, "proprietario dell'auto" e "proprietario del cane" sono di tipi diversi e le cose che potresti voler fare con l'una potrebbero non essere applicabili all'altra.

(Allo stesso modo, quando diciamo che Maybeè gentile * -> *in Haskell, intendiamo che è assurdo (formalmente; informalmente lo facciamo tutto il tempo) parlare di avere una Maybe" a ". Invece, possiamo avere una Maybe Into una Maybe String, poiché quelle sono cose di gentile *.)

Quindi il punto principale di parlare dei tipi è in modo che possiamo formalizzare il nostro ragionamento intorno a parole come "proprietario" e far valere che prendiamo sempre e solo valori di tipi che sono stati "completamente costruiti" e non privi di senso.


1
Non sto dicendo che la tua analogia sia sbagliata, ma penso che possa causare confusione. Dijkstra ha alcune parole sulle analogie. Google "Sulla crudeltà di insegnare davvero la scienza informatica".
Rafael Castro,

Voglio dire, ci sono analogie con le auto, e poi ci sono analogie con le auto. Non penso che evidenziare la struttura di tipo implicita in un linguaggio naturale (che, devo ammettere, ho allungato nella seconda metà) come un modo per spiegare un sistema di tipo formale sia lo stesso tipo di insegnamento attraverso l'analogia di cui si parla ciò che un programma "vuole" fare.
user11228628

1

A quanto ho capito, un tipo è un tipo di tipo.

Esatto, quindi esploriamo cosa significa. Into Textsono tipi concreti, ma Maybe aè un tipo astratto . Non diventerà un tipo concreto fino a quando non deciderai quale valore specifico desideri aper una particolare variabile (o valore / espressione / qualunque cosa) ad es Maybe Text.

Diciamo che Maybe aè un costruttore di tipi perché è come una funzione che accetta un singolo tipo di calcestruzzo (ad esempio Text) e restituisce un tipo di calcestruzzo ( Maybe Textin questo caso). Ma altri costruttori di tipi potrebbero richiedere ancora più "parametri di input" prima di restituire un tipo concreto. es. Map k vdeve prendere due tipi concreti (ad es. Inte Text) prima di poter costruire un tipo concreto ( Map Int Text).

Quindi, i costruttori Maybe ae List atype hanno la stessa "firma" che denotiamo * -> *(in modo simile alla firma della funzione di Haskell) perché se si dà loro un tipo concreto sputeranno un tipo concreto. Lo chiamiamo il "tipo" del tipo Maybee Listabbiamo lo stesso tipo.

Si dice che i tipi concreti siano gentili *, e il nostro esempio di Mappa è gentile * -> * -> *perché prende due tipi concreti come input prima di poter produrre un tipo concreto.

Puoi vedere che si tratta principalmente del numero di "parametri" che passiamo al costruttore di tipi, ma ti rendi conto che potremmo anche avere costruttori di tipi nidificati all'interno di costruttori di tipi, in modo da poter finire con un tipo che assomiglia * -> (* -> *) -> *ad esempio .

Se sei un appassionato di Scala / Java, potresti trovare utile questa spiegazione: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind


Questo non è corretto In Haskell, distinguiamo tra Maybe aun sinonimo di forall a. Maybe atipo polimorfico *e Maybeun tipo monomorfo * -> *.
b0fh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.