La regola base di base è che nelle funzioni di programmazione FP fa 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 permette di avere "variabili d'istanza" nella tua funzione con chiusure / scoping 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 prossima domanda è cosa intendi per interfaccia? Un approccio usa le interfacce nominali (è conforme all'interfaccia se dice che lo fa) - questo di solito dipende molto dalla lingua che stai usando, quindi lasciamolo per quest'ultimo. L'altro modo per definire un'interfaccia è il modo strutturale, vedere quali parametri ricevono e restituiscono. Questo è il tipo di interfaccia che si tende a vedere in linguaggi dinamici a dattilografia e si adatta molto bene a tutti i FP: un'interfaccia è solo il tipo di parametri di input per le nostre funzioni e i tipi restituiti in modo che tutte le funzioni corrispondano al i tipi corretti si adattano all'interfaccia!
Pertanto, il modo più semplice per rappresentare un oggetto che corrisponde a un'interfaccia è semplicemente avere un gruppo di funzioni. Di solito aggiri il brutto passaggio delle funzioni separatamente comprimendole in una sorta di record:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
L'utilizzo di funzioni nude o record di funzioni farà molto per risolvere la maggior parte dei tuoi problemi comuni in modo "senza grassi" senza tonnellate di piastre. Se hai bisogno di qualcosa di più avanzato, a volte le lingue ti offrono funzionalità extra. Un esempio menzionato dalle persone è il tipo di classi Haskell. Le classi di tipi associano essenzialmente un tipo a uno di quei record di funzioni e ti consentono di scrivere le cose in modo che i dizionari siano impliciti e passano automaticamente 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 sui typeclass è che i dizionari sono associati ai tipi e non ai valori (come succede nel dizionario e nelle versioni OO). Ciò significa che il sistema di tipi non ti consente di mixare "tipi" [1]. Se vuoi una lista di "blargables" o di una funzione binaria che va a blargables allora le typeclass vincoleranno tutto per essere lo stesso tipo mentre l'approccio del dizionario ti permetterà di avere blargables di origini diverse (la versione migliore dipende molto da ciò che sei facendo)
[1] Esistono modi avanzati di fare "tipi esistenziali", ma di solito non ne vale la pena.