Depuis de nombreuses années de programmation OO j'ai compris ce que sont les unions discriminées, mais je ne les ai jamais vraiment ratées. J'ai récemment fait de la programmation fonctionnelle en C # et maintenant je trouve que je continue à souhaiter Cela me déconcerte car, à première vue, le concept d'unions discriminées semble tout à fait indépendant de la dichotomie fonctionnelle/OO.
Y a-t-il quelque chose d'inhérent à la programmation fonctionnelle qui rend les syndicats discriminés plus utiles qu'ils ne le seraient en OO, ou est-ce qu'en me forçant à analyser le problème d'une "meilleure" manière, j'ai simplement relevé mes normes et maintenant j'exige une meilleure modèle?
Les unions discriminées brillent vraiment en conjonction avec la correspondance de modèles, où vous sélectionnez un comportement différent selon les cas. Mais ce modèle est fondamentalement antithétique à pur OO principes.
En OO pur, les différences de comportement doivent être définies par les types (objets) eux-mêmes et encapsulées. Ainsi, l'équivalence de la correspondance de motifs serait d'appeler une seule méthode sur l'objet lui-même, qui est ensuite surchargée par les sous-types en question pour définir un comportement différent. L'inspection du type d'un objet de l'extérieur (ce que fait la correspondance de motifs) est considérée comme un contre-modèle.
La différence fondamentale est que les données et le comportement sont séparés dans la programmation fonctionnelle, tandis que les données et le comportement sont encapsulés ensemble dans OO.
Telle est la raison historique. Un langage comme C # évolue d'un langage classique OO vers un langage multi-paradigmes en incorporant de plus en plus de fonctionnalités.
Ayant programmé en Pascal et Ada avant d'apprendre la programmation fonctionnelle, je n'associe pas les unions discriminées à la programmation fonctionnelle.
Les syndicats discriminés sont en quelque sorte le double de l'héritage. Le premier permet d'ajouter facilement des opérations sur un ensemble fixe de types (ceux de l'union), et l'héritage permet d'ajouter facilement des types avec un ensemble fixe d'opérations. (Comment ajouter facilement les deux s'appelle le problème d'expression ; c'est un problème particulièrement difficile pour les langues avec un système de type statique.)
En raison de l'accent mis sur OO sur les types, et du double accent de la programmation fonctionnelle sur les fonctions, les langages de programmation fonctionnels ont une affinité naturelle pour les types d'union et offrent des structures syntaxiques pour faciliter leur utilisation.
Les techniques de programmation impératives, souvent utilisées en OO, reposent souvent sur deux modèles:
null
pour indiquer "aucune valeur" ou échec.Le paradigme fonctionnel évite généralement les deux, préférant renvoyer un type composé qui indique une raison de succès/échec ou une valeur/aucune valeur.
Les syndicats discriminés font l'affaire pour ces types de composés. Par exemple, dans la première instance, vous pouvez renvoyer true
ou une structure de données décrivant l'échec. Dans le second cas, une union qui contient une valeur, ou none
, nil
etc. Le deuxième cas est si courant que de nombreux langages fonctionnels ont un type "peut-être" ou "option" construit -in pour représenter cette union valeur/aucune.
Lorsque vous passez à un style fonctionnel avec, par exemple, C #, vous trouverez rapidement un besoin pour ces types de composés. void/throw
et null
ne se sentent pas bien avec un tel code. Et les syndicats discriminés (UD) correspondent bien au projet de loi. Ainsi, vous vous êtes retrouvé à les vouloir, tout comme beaucoup d'entre nous.
La bonne nouvelle est qu'il existe de nombreuses bibliothèques qui modélisent les DU, par exemple en C # (jetez un œil à ma propre bibliothèque Succinc <T> par exemple).
Les types de somme seraient généralement moins utiles dans les langages courants OO car ils résolvent un type de problème similaire au sous-typage OO. Une façon de les examiner est qu'ils gèrent tous les deux le sous-typage mais OO est open
c'est-à-dire que l'on peut ajouter des sous-types arbitraires à un type parent et que les types de somme sont closed
c'est-à-dire qu'on détermine à l'avance quels sont les sous-types valide.
Maintenant, de nombreux langages OO combinent le sous-typage avec d'autres concepts tels que les structures héritées, le polymorphisme, le typage de référence, etc. pour les rendre généralement plus utiles. En conséquence, ils ont tendance à nécessiter plus de travail à configurer ( avec les classes et les constructeurs et ainsi de suite), donc ils ne sont généralement pas utilisés pour des choses comme Result
s et Option
s et ainsi de suite jusqu'à ce que le typage générique devienne courant.
Je dirais également que l'accent mis sur les relations réelles que la plupart des gens ont apprises quand ils ont commencé OO programmation par exemple Dog isa Animal, signifiait que Integer isa Result ou Error isa Result semble un peu étranger. Bien que les idées soient assez similaires.
Quant aux raisons pour lesquelles les langages fonctionnels pourraient préférer la frappe fermée à la frappe ouverte, une des raisons possibles est qu'ils ont tendance à préférer la correspondance des modèles. C'est utile pour le polymorphisme de fonction mais cela fonctionne aussi très bien avec les types fermés car le compilateur peut vérifier statiquement que la correspondance couvre tous les sous-types. Cela peut rendre le langage plus cohérent même si je ne pense pas qu'il y ait un avantage inhérent (je peux me tromper).