La règle de base est que dans la programmation FP, les fonctions font le même travail que les objets dans la programmation OO. Vous pouvez appeler leurs méthodes (enfin, la méthode "call" de toute façon) et ils répondent selon certaines règles internes encapsulées. En particulier, chaque langage FP décent vous permet d'avoir des "variables d'instance" dans votre fonction avec des fermetures / portée lexicale.
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;
}
};
Maintenant, la question suivante est ce que vous entendez par une interface? Une approche consiste à utiliser des interfaces nominales (elle se conforme à l'interface si elle le dit) - celle-ci dépend généralement beaucoup de la langue que vous utilisez, alors laissons-la pour cette dernière. L'autre façon de définir une interface est la manière structurelle, de voir quels paramètres la chose reçoit et retourne. C'est le type d'interface que vous avez tendance à voir dans les langages dynamiques de type canard et il correspond très bien à tous les FP: une interface est juste les types des paramètres d'entrée de nos fonctions et les types qu'ils renvoient donc Toutes les fonctions correspondant à la les bons types correspondent à l'interface!
Par conséquent, la façon la plus simple de représenter un objet correspondant à une interface est simplement d'avoir un groupe de fonctions. Vous contournez généralement la laideur de passer les fonctions séparément en les emballant dans une sorte d'enregistrement:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
L'utilisation de fonctions nues ou d'enregistrements de fonctions contribuera grandement à résoudre la plupart de vos problèmes courants de manière "sans gras" sans tonnes de passe-partout. Si vous avez besoin de quelque chose de plus avancé que cela, parfois les langues vous offrent des fonctionnalités supplémentaires. Un exemple que les gens ont mentionné est celui des classes de type Haskell. Les classes de types associent essentiellement un type à l'un de ces enregistrements de fonctions et vous permettent d'écrire des choses afin que les dictionnaires soient implicites et soient automatiquement transmis aux fonctions internes selon le cas.
-- 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)
Cependant, une chose importante à noter à propos des classes de types est que les dictionnaires sont associés aux types et non aux valeurs (comme ce qui se passe dans les versions dictionnaire et OO). Cela signifie que le système de type ne vous permet pas de mélanger des "types" [1]. Si vous voulez une liste de "blargables" ou une fonction binaire prenant les blargables, alors les classes de caractères contraindront tout à être du même type tandis que l'approche par dictionnaire vous permettra d'avoir des blargables d'origines différentes (quelle version est la meilleure dépend beaucoup de ce que vous êtes) Faire)
[1] Il existe des moyens avancés de faire des "types existentiels" mais cela ne vaut généralement pas la peine.