La risposta finora è fuorviante.
È necessario operare una distinzione tra polimorfismo "parametrico" e "sovraccarico ad hoc". Parametrico significa "si comporta in modo uniforme per tutti i tipi a", mentre "ad-hoc" - quello che Simon chiama polimorfico - cambia l'implementazione in base al tipo.
Esempi di entrambi sono reverse :: [a] -> [a]
, che è parametrico e show :: Show a => a -> String
che è "ad hoc" sovraccarico.
Se vuoi più di un'intuizione astratta, penso che aiuti a considerare le classi di verbi del linguaggio naturale che "funzionano" per tutti gli oggetti, come "possedere" o "pensare" che non pongono vincoli sull'oggetto, ma " aprire "richiede che si possa aprire ciò di cui stiamo parlando. Posso "pensare a una porta" e "aprire una porta", mentre non ha senso, ad esempio, "aprire un albero". Per dare l'esempio ancora di più "aprire" è "polimorfico ad hoc" come "aprire una finestra" e "aprire un ticket di reclamo con il servizio clienti" sono due cose molto diverse. Se questo sembra forzato, dimenticalo! Per me funziona.
Entrambi vengono risolti in fase di compilazione, tuttavia, e in effetti "cancellati". Modulo estensioni varie GHC e Template Haskell, ecc Tipi sono infatti cancellati in fase di compilazione e non vengono mai controllati in fase di esecuzione.
Le funzioni parametricamente polimorfiche si comportano in modo identico per tutti i tipi, quindi è necessario generare solo un pezzo di codice, mentre il compilatore decide in fase di compilazione quale versione di una funzione "classificata per tipo" deve essere eseguita in un determinato punto del programma. Questo è anche il motivo per cui esiste la limitazione di un'istanza per tipo per classe di tipo e la soluzione "newtype" corrispondente.
L'implementazione è dettagliata nel libro di testo SPJ e nel documento di Wadler e Blotts sulle classi di tipo .
a -> String
. Probabilmente avresti un vincolo di tipo, comeShow a => a -> String
.