Quel problème les types de données algébriques résolvent-ils?

18

Fair Warning, je suis nouveau à la programmation fonctionnelle afin que je puisse tenir de nombreuses hypothèses mauvaises.

J'ai appris sur les types algébriques. De nombreux langages fonctionnels semblent en avoir, et ils sont assez utiles en conjonction avec la correspondance de modèles. Cependant, quel problème résolvent-ils réellement? Je peux implémenter un type algébrique apparemment (en quelque sorte) en C # comme ceci:

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

Mais je pense que la plupart seraient d'accord c'est un abâtardissement de la programmation orientée objet, vous ne devriez jamais faire. La programmation fonctionnelle ajoute-t-elle simplement une syntaxe plus propre qui rend ce style de programmation moins grossier? Que manque-t-il d'autre?

ConditionRacer
la source
6
La programmation fonctionnel est un modèle différent de la programmation orientée objet.
Basile Starynkevitch
@BasileStarynkevitch Je me rends compte, mais il y a langues comme F # qui sont un peu des deux. La question est pas tant de langages fonctionnels, mais plus sur les types de données algébriques de problèmes à résoudre.
ConditionRacer
7
Qu'est - ce qui se passe quand je définis class ThirdOption : Option{}et vous donner ce new ThirdOption()que vous attendiez Someou None?
amon
1
Les langages @amon qui ont des types de somme ont généralement des moyens de l'interdire. Par exemple, Haskell définit data Maybe a = Just a | Nothing(équivalent à data Option a = Some a | Nonedans votre exemple): vous ne pouvez pas ajouter un troisième cas post-hoc. Alors que vous pouvez sorte de types sum Emuler en C # de la manière que vous avez montré, ce n'est pas la plus jolie.
Martijn
1
Je pense qu'il est moins « ce problème ne ADTs Solve » et plus comme « ADTs sont une façon d'aborder les choses ».
MathematicalOrchid

Réponses:

44

Les classes avec des interfaces et présentes héritage un monde ouvert: le monde peut ajouter un nouveau type de données. Pour une interface donnée, il peut y avoir des classes l'implémentant partout dans le monde, dans différents fichiers, dans différents projets, dans différentes entreprises. Ils le rendent facile à ajouter des cas aux structures de données, mais parce que les implémentations de l'interface est décentralisée, il est difficile d'ajouter une méthode à l'interface. Une fois que l'interface est publique, il est essentiellement gelé. On ne sait pas toutes les applications possibles.

Types de données algébriques sont les deux à cela, elles sont fermées . Tous les cas des données sont répertoriés en un seul endroit et non seulement les opérations peuvent répertorier les variantes de manière exhaustive, mais elles sont encouragées à le faire. Par conséquent, écrire une nouvelle fonction fonctionnant sur un type de données algébrique est trivial: il suffit d'écrire la putain de fonction. En retour, l'ajout de nouveaux cas est compliqué car vous devez parcourir la base de code entière et l'étendre tous match. Semblable à la situation avec les interfaces, dans la bibliothèque standard de Rust, l' ajout d'une nouvelle variante est un changement de rupture (pour les types publics).

Ce sont les deux faces du problème d'expression . Types de données algébriques sont une solution incomplète, mais est donc POO. Les deux présentent des avantages en fonction du nombre de cas de données, de la fréquence à laquelle ces cas changent et de la fréquence à laquelle les opérations sont étendues ou modifiées. (C'est pourquoi de nombreux langages modernes proposent les deux, ou quelque chose de similaire, ou vont directement vers des mécanismes plus puissants et plus compliqués qui tentent de subsumer les deux approches.)


la source
Obtenez-vous une erreur de compilation si vous ne mettez pas à jour une correspondance, ou est-ce uniquement au moment de l'exécution que vous le découvrez?
Ian
6
@Ian La plupart des langages fonctionnels typés et vérifier statiquement exhaustivité de filtrage. Cependant, s'il y a un modèle « attraper », le compilateur est heureux , même si la fonction devrait traiter avec le nouveau cas pour faire son travail.
De plus, la vérification statique qu'un modèle est exhaustif est cher en général.
usr
@usr Pas de langage que je suis au courant de tentatives une solution parfaite, soit le compilateur comprenne qu'il est exhaustif ou que vous êtes obligé d'ajouter un attrape-tout cas où vous crash and burn. Je ne sais pas d'une relation à SAM. si, vous avez un lien à une réduction? Quelle que soit, pour le code écrit réelle dans les programmes réels, la vérification est une exhaustivité goutte dans le seau.
Imaginez que vous correspondant sur N booléens. Ensuite , vous insère des dispositions match comme (a, b, _, d, ...). Le attrape-tout cas est alors! Clause1 &&! Clause2 && .... qui ressemble à moi sam..
usr
12

La programmation fonctionnelle ajoute-t-elle simplement une syntaxe plus propre qui rend ce style de programmation moins grossier?

C'est une simplification peut-être, mais oui.

Que manque-t-il d'autre?

Soyons clairs sur les types de données algébriques (résumant ce lien fin de Learn you as Haskell):

  • Un type de somme qui dit « cette valeur peut être un A ou un B ».
  • Un type de produit qui dit « est à la fois cette valeur un A et B ».

votre exemple ne fonctionne vraiment avec la première.

C # a struct, les classes, les énumérations, les génériques sur le dessus, et ceux des tas de règles régissant la manière dont ces comportements sont.

Combinés à une certaine syntaxe pour aider, les langages fonctionnels peuvent décomposer les opérations jusqu'à ces deux chemins, offrant une approche propre, simple et élégante des types.

Quel problème les types de données algébriques résolvent-ils?

Ils résolvent le même problème que tout autre système de type: « Quelles sont les valeurs légal d'utiliser ici? » - ils prennent juste une approche différente.

Telastyn
la source
4
Votre dernière phrase est un peu comme dire à quelqu'un que "les navires résolvent le même problème que les avions: le transport - ils ont juste une approche différente".
Mehrdad
@mehrdad - Je pense que ce qu'il exagérant un peu.
1
Vous comprenez sans doute très bien les types de données algébriques, mais votre liste à puces ("Un type de somme est ..." et "Un type de produit est ...") ressemble plus à une description des types d'union et d'intersection, qui ne sont pas tout à fait la même chose que la somme et les types de produits.
pyon
6

Cela peut vous surprendre d'apprendre que pattern matching est pas considéré comme le meilleur moyen idiomatiques de travailler avec les options. Voir la documentation des options de Scala pour plus à ce sujet.

La plupart du temps ce que vous manquez est il y a un tas de fonctions créées pour rendre le travail plus facile avec options. Prenons l'exemple principal de la documentation Scala:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

Remarquez comment mapet filterlaissez vous enchaînez les opérations sur les options, sans avoir à vérifier chaque point si vous avez Noneou non. Puis à la fin, vous pouvez utilisergetOrElse pour spécifier une valeur par défaut. A aucun moment que tu fais quelque chose de « brut » comme types de vérification. Haskell a son propre ensemble de fonctions analogiques, sans parler du grand ensemble de fonctions qui fonctionnera sur n'importe quelle monade ou foncteur.

De même, lorsque vous créez vos propres types, vous êtes censé fournir des fonctions similaires à travailler avec eux.

Karl Bielefeldt
la source