Esistono diversi meccanismi di polimorfismo. Ad eccezione del polimorfismo parametrico, questi flussi di controllo di distribuzione dipendono dal tipo di argomenti della funzione. In altre parole: sono una specie di overloading di funzioni . Gli argomenti possono essere considerati per il loro tipo statico o per il loro tipo dinamico. Se inviamo una chiamata su un tipo statico, chiamiamo questo polimorfismo ad-hoc . Se inviamo il tipo dinamico, è single dispatch se consideriamo solo il tipo dinamico di un argomento (in genere un oggetto su cui viene chiamato un metodo) o dispatch multiplo se consideriamo il tipo dinamico di tutti gli argomenti.
Tutte le lingue OOP devono almeno offrire una singola spedizione. Il pattern Visitor può essere utilizzato per simulare dispatch multipli utilizzando livelli aggiuntivi di riferimento indiretto, ma non è pratico per più di due argomenti. Multi-dispatch in generale risolve lo stesso tipo di problemi che il polimorfismo ad-hoc fa, tranne che accade a runtime e può quindi essere più espressivo. Esempi degni di nota di lingue con multi-dispatch (a volte chiamati multi-metodi) sono il Common Lisp Object System (CLOS) e la lingua di Julia.
Il post del blog a cui ti sei collegato dimostra un polimorfismo ad-hoc, ma in modo eccessivamente complicato. L'esempio più semplice potrebbe essere:
abstract class Pet
class Cat extends Pet
class Dog extends Pet
object Owner {
def pet(cat: Cat) = "You can haz cuddles!"
def pet(dog: Dog) = "Who's a good boy?"
}
val cat: Cat = new Cat()
val dog: Dog = new Dog()
val pet: Pet = new Cat()
Owner pet cat // works
Owner pet dog // works
Owner pet pet // fails, because implementation is chosen *statically*
È la stessa storia in Java e C ++. Il post del blog diventa quindi tangente su come utilizzare i parametri impliciti per implementare i metodi di estensione e come possono essere utilizzati per nascondere il pattern del visitatore. Niente di tutto questo è davvero rilevante per il polimorfismo ad-hoc.
Il tuo esempio di codice ha dei problemi in quanto i tipi di ritorno di tooCleverFunction
ma, più importante, del condizionale assegnato alla variabile y
non sono ben definiti. Poiché y
può essere Int
o Rational
, il tipo di controllo deve rifiutare il tuo codice. Per consentire in modo sicuro questa ambiguità, potremmo introdurre un tipo Union
. Ma se abbiamo y :: Union Int Rational
, allora veryCleverFunction
non può essere applicato a y
(eccetto ovviamente se Union
è un functor in modo che possiamo fare fmap veryCleverFunction y
, che restituisce un altro Union
per te da trattare con). Se usiamo una tale unione, nessuna informazione di tipo statico viene persa. Invece, devi affrontare il casino che hai creato nel resto del programma.
Vorresti y
per avere invece il tipo Num a
. Ma Num a
non è un tipo, ma una classe di tipo, cioè una restrizione sul tipo a
.