Quelle est l'alternative de programmation fonctionnelle à une interface?

15

Si je veux programmer dans un style "fonctionnel", avec quoi remplacer une interface?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Peut-être un Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

La seule raison pour laquelle j'utilise une interface en premier lieu est parce que je veux toujours que certaines propriétés / méthodes soient disponibles.


Edit: Plus de détails sur ce que je pense / tente.

Disons que j'ai une méthode qui prend trois fonctions:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

Avec une instance de Barje peux utiliser cette méthode:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

Mais c'est un peu pénible car je dois le mentionner bartrois fois en un seul appel. De plus, je n'ai pas vraiment l'intention que les appelants fournissent des fonctions à partir de différentes instances

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

En C #, an interfaceest une façon de gérer cela; mais cela semble être une approche très orientée objet. Je me demande s'il existe une solution plus fonctionnelle: 1) passer le groupe de fonctions ensemble, et 2) s'assurer que les fonctions sont correctement liées les unes aux autres.

Ðаn
la source
Tu ne le ferais pas. Les interfaces pour les types de données sont parfaitement correctes (bien que vous préfériez les objets immuables).
Telastyn
1
Le chapitre 2 du SICP est à peu près à ce sujet.
user16764
7
Après avoir relu votre question, je suis curieux de savoir quelles fonctionnalités spécifiques vous essayez d'accomplir? Ce que vous semblez demander, c'est comment faire un programme à effet secondaire de style oo contre une instance dans un style fonctionnel, ce qui n'a pas de sens.
Jimmy Hoffa
La réponse serait dépendante de la langue. Dans Clojure, vous pouvez utiliser clojure.org/protocols , où la seule zone souple est le type de paramètres sur lesquels les fonctions doivent fonctionner - ils sont un objet - c'est tout ce que vous savez.
Job
1
Rendez-le simple: une structure contenant ces pointeurs de méthode, plus une fonction pour l'initialiser à partir d'une instance d'objet. Pourquoi Haskell? ;)
mlvljr

Réponses:

6

Ne traitez pas la programmation fonctionnelle comme un placage fin par rapport à la programmation impérative; il y a bien plus qu'une simple différence syntaxique.

Dans ce cas, vous avez une GetIDméthode, ce qui implique l'unicité des objets. Ce n'est pas une bonne approche pour écrire des programmes fonctionnels. Vous pourriez peut-être nous dire le problème que vous essayez de résoudre et nous pourrions vous donner des conseils plus significatifs.

dan_waterworth
la source
3
"Vous devez penser en russe."
Ðаn
C'est suffisant; mon vrai problème n'est pas particulièrement intéressant. J'ai déjà des choses qui fonctionnent très bien en C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay si vous voulez vraiment regarder le code), mais ce serait amusant de le faire dans un style plus fonctionnel tout en utilisant toujours C #.
Ðаn
1
@ Ðаn Est-ce vraiment une citation de Firefox (film) (parce que c'est génial si c'est le cas)? Ou est-il utilisé ailleurs?
icc97
2
Là où je suis d'accord, c'est un changement de paradigme complet de la programmation impérative à la programmation fonctionnelle, il y a plusieurs ordres de grandeur plus de lignes de code écrites de manière impérative. Donc, beaucoup plus de cas d'angle écrits pour des systèmes énormes auront été trouvés avec une programmation impérative. Il y a beaucoup de bonnes pratiques dans la programmation impérative et savoir si ces compétences peuvent être traduites ou si elles ne sont pas un problème en PF est une question digne. Il est tout à fait possible d'écrire du code horrible en FP, donc ce genre de questions devrait également mettre en évidence les bonnes parties de FP.
icc97
11

Haskell et ses dérivés ont des classes de types similaires aux interfaces. Bien qu'il semble que vous vous demandiez comment effectuer l'encapsulation, c'est une question concernant les systèmes de types. Le système de type Hindley Milner est courant dans les langages fonctionnels, et il a des types de données qui le font pour vous de manière variable selon les langues.

Jimmy Hoffa
la source
5
+1 pour les classes de types - la principale différence entre une classe de types Haskell et une interface Java est que la classe de types est associée au type une fois les deux déclarées séparément. Vous pouvez utiliser un ancien type via une nouvelle "interface" aussi facilement que vous pouvez utiliser une ancienne "interface" pour accéder à un nouveau type. Pour masquer les données, vous masquez l'implémentation du type dans un module. Au moins selon Bertrand Meyer de la renommée Eiffel , une classe OOP est une sorte de module.
Steve314
5

Il existe plusieurs façons de permettre à une fonction de gérer plusieurs entrées.

Premier et plus courant: le polymorphisme paramétrique.

Cela permet à une fonction d'agir sur des types arbitraires:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

Ce qui est bien, mais ne vous donne pas la répartition dynamique des interfaces OO. Pour cela, Haskell a des classes de caractères, Scala a des implications, etc.

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Entre ces deux mécanismes, vous pouvez exprimer toutes sortes de comportements complexes et intéressants sur vos types.

Daniel Gratzer
la source
1
Vous pouvez ajouter des conseils de surbrillance sintax lorsque la langue de la réponse ne correspond pas à la langue de la question. Voir ma modification suggérée par exemple.
hugomg
1

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.

hugomg
la source
0

Je pense que ça va être spécifique à la langue. Je viens d'un fond vif. Dans de nombreux cas, les interfaces avec l'état cassent le modèle fonctionnel dans une certaine mesure. Ainsi, CLOS, par exemple, est l'endroit où LISP est moins fonctionnel et plus proche d'un langage impératif. Généralement, les paramètres de fonction requis combinés avec des méthodes de niveau supérieur sont probablement ce que vous recherchez.

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
ipaul
la source