La regola empirica di base è che nelle funzioni di programmazione FP svolgono lo stesso lavoro degli oggetti nella programmazione OO. Puoi chiamare i loro metodi (beh, comunque il metodo "call") e rispondono secondo alcune regole interne incapsulate. In particolare, ogni linguaggio FP decente là fuori ti consente di avere "variabili di istanza" nella tua funzione con chiusure / ambito lessicale.
var make_OO_style_counter = function(){
return {
counter: 0
increment: function(){
this.counter += 1
return this.counter;
}
}
};
var make_FP_style_counter = function(){
var counter = 0;
return fucntion(){
counter += 1
return counter;
}
};
Ora la domanda successiva è cosa intendi con un'interfaccia? Un approccio sta usando le interfacce nominali (è conforme all'interfaccia se dice che lo fa) - questo di solito dipende molto dal linguaggio che stai usando, quindi lascialo per quest'ultimo. L'altro modo di definire un'interfaccia è il modo strutturale, vedendo quali parametri la cosa riceve e ritorna. Questo è il tipo di interfaccia che tendi a vedere in linguaggi dinamici, tipizzati da papera e si adatta molto bene a tutti i FP: un'interfaccia è solo i tipi di parametri di input per le nostre funzioni e i tipi che restituiscono, quindi Tutte le funzioni corrispondenti al tipi corretti si adattano all'interfaccia!
Pertanto, il modo più semplice di rappresentare un oggetto corrispondente a un'interfaccia è semplicemente avere un gruppo di funzioni. Di solito si aggira la bruttezza di passare le funzioni separatamente impacchettandole in una sorta di record:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
L'uso di funzioni nude o di registrazioni di funzioni farà molto per risolvere la maggior parte dei problemi comuni in modo "privo di grassi" senza tonnellate di scaldabagno. Se hai bisogno di qualcosa di più avanzato di quello, a volte le lingue ti offrono funzionalità extra. Un esempio di persone citate sono le classi di tipo Haskell. Le classi di tipi essenzialmente associano un tipo a uno di quei record di funzioni e ti permettono di scrivere cose in modo che i dizionari siano impliciti e vengano automaticamente passati alle funzioni interne come appropriato.
-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
blarg_name :: String,
blarg_id :: Integer
}
do_something :: BlargDict -> IO()
do_something blarg_dict = do
print (blarg_name blarg_dict)
print (blarg_id blarg_dict)
-- Typeclass version
class Blargable a where
blag_name :: a -> String
blag_id :: a -> String
do_something :: Blargable a => a -> IO
do_something blarg = do
print (blarg_name blarg)
print (blarg_id blarg)
Una cosa importante da notare sulle calotte è che i dizionari sono associati ai tipi e non ai valori (come ciò che accade nel dizionario e nelle versioni OO). Ciò significa che il sistema di tipi non consente di mescolare "tipi" [1]. Se vuoi un elenco di "argomenti" o una funzione binaria che porti a argomenti, allora le typeclass vinceranno tutto allo stesso tipo mentre l'approccio del dizionario ti permetterà di avere obiettivi di origini diverse (quale versione è meglio dipende molto da ciò che sei facendo)
[1] Ci sono modi avanzati per fare "tipi esistenziali" ma di solito non vale la pena.