Sur de nombreux articles, décrivant les avantages de la programmation fonctionnelle, j'ai vu des langages de programmation fonctionnelle, tels que Haskell, ML, Scala ou Clojure, appelé "Langues déclaratives" distinctes des langues impératives telles que C/C++/C #/Java. Ma question est ce qui rend les langues de programmation fonctionnelles déclaratives par opposition à l'impératif.
Une explication souvent rencontrée décrivant les différences entre la programmation déclarative et impérative est que dans la programmation impérative, vous dites à l'ordinateur "Comment faire quelque chose" par opposition à "Que faire" dans les langues déclaratives. Le problème que j'ai avec cette explication est que vous faites constamment dans toutes les langues de programmation. Même si vous descendez à l'assemblage de niveau le plus bas, vous racontez toujours l'ordinateur "Que faire", vous dites à la CPU d'ajouter deux numéros, vous ne l'indiquez pas sur la manière de procéder à l'addition. Si nous allons à l'autre extrémité du spectre, un langage fonctionnel pur de haut niveau comme Haskell, vous indiquez en fait à l'ordinateur comment atteindre une tâche spécifique, c'est-à-dire ce que votre programme est une séquence d'instructions pour obtenir une tâche spécifique qui L'ordinateur ne sait pas comment réaliser seul. Je comprends que des langues telles que Haskell, Clojure, etc. sont évidemment plus élevées que C/C++/C #/Java et offrent des fonctionnalités telles que l'évaluation paresseuse, les structures de données immuables, les fonctions anonymes, le currying, les structures de données persistantes, etc. Tout ce qui fait Programmation fonctionnelle possible et efficiente, mais je ne les classerais pas comme des langues déclaratives.
Une des langues déclaratives pures pour moi serait une qui est entièrement composée de déclarations uniquement, un exemple de telles langues serait CSS (oui je sais que CSS ne serait techniquement pas un langage de programmation). CSS contient simplement des déclarations de style qui sont utilisées par le HTML et JavaScript de la page. CSS ne peut rien faire d'autre que de faire des déclarations, il ne peut pas créer de fonctions de classe, c'est-à-dire des fonctions qui déterminent le style à afficher en fonction de certains paramètres, vous ne pouvez pas exécuter des scripts CSS, etc. que pour moi décrit une langue déclarative (avis que je n'ai pas dit déclaratif programmation Langue).
Mise à jour :
Je joue récemment avec Prologs et à moi, Prolog est le langage de programmation le plus proche de la langue de programmation entièrement déclarative (au moins à mon avis), s'il n'est pas le seul langage de programmation entièrement déclaratif. Pour élaborer une programmation à PROLO, effectue des déclarations qui indiquent un fait (une fonction de prédicat qui revient pour une entrée spécifique) ou une règle (une fonction de prédicat qui retourne true pour une condition/modèle donnée en fonction des intrants), des règles sont définis en utilisant une technique de correspondance de motif. Pour faire quoi que ce soit dans PRAGolog, vous interrogez la base de connaissances en remplaçant une ou plusieurs des entrées d'un prédicat avec une variable et PRAGolog essaie de trouver des valeurs pour les variables (s) pour lesquelles le prédicat réussit.
Mon point est à PRologit, il n'y a pas d'instructions impératives, vous racontez essentiellement (déclarer) l'ordinateur ce qu'il sait, puis demandant (interrogateur) sur les connaissances. Dans les langages de programmation fonctionnelle, vous donnez toujours des instructions. Prenez une valeur, une fonction d'appel X et l'ajoutez 1 à celle-ci, etc., même si vous ne manipulez pas directement les emplacements de mémoire ni l'écriture de calcul de l'étape par étape. Je ne dirais pas que la programmation à Haskell, ml, Scala ou Clojure est déclarative en ce sens, bien que je puisse me tromper. Est une véritable programmation fonctionnelle de la programmation fonctionnelle dans le sens où je décrit ci-dessus.
Vous semblez dessiner une ligne entre déclarer des choses et instruire une machine. Il n'y a pas de séparation de ce type et rapide. Étant donné que la machine qui est instructuée de la programmation impérative n'a pas besoin de matériel physique, il y a beaucoup de liberté pour l'interprétation. Presque tout peut être considéré comme un programme explicite pour la bonne machine abstraite. Par exemple, vous pouvez voir CSS comme langage de niveau plutôt élevé pour la programmation d'une machine qui résout principalement les sélecteurs et définit les attributs des objets Dom ainsi sélectionnés.
La question est de savoir si une telle perspective est sensible et inversement, quelle que soit la séquence des instructions ressemble à une déclaration du résultat étant calculé. Pour CSS, la perspective déclarative est clairement plus utile. Pour C, la perspective impérative est clairement prédominante. Quant à des langues que Haskell, eh bien ...
La langue a spécifié la sémantique concrète. C'est bien sûr, on peut interpréter le programme en tant que chaîne d'opérations. Cela ne prend même pas trop d'efforts pour choisir les opérations primitives afin qu'elles maptien sur le matériel de produits de base (c'est ce que font la machine STG et d'autres modèles).
Cependant, la façon dont les programmes de haskell sont écrits, ils peuvent être lus raisonnablement comme une description du résultat à calculer. Prenons, par exemple, le programme de calcul de la somme des premiers N facteurs factoriels:
sum_of_fac n = sum [product [1..i] | i <- ..n]
Vous pouvez descendre cela et le lire comme une séquence d'opérations STG, mais beaucoup plus naturelle est de le lire comme une description du résultat (qui est, Je pense, une définition plus utile de la programmation déclarative que "quoi de calcul"): le résultat est la somme des produits de [1..i]
Pour tout i
= 0, ..., n. Et c'est beaucoup plus déclaratif que presque tout programme ou fonction C.
L'unité de base d'un programme impératif est la déclaration. Les déclarations sont exécutées pour leurs effets secondaires. Ils modifient l'état qu'ils reçoivent. Une séquence d'instructions est une séquence de commandes, indiquant le faire alors faites cela. Le programmeur spécifie l'ordre exact pour effectuer le calcul. C'est ce que les gens veulent dire en disant à l'ordinateur comment de le faire.
L'unité de base d'un programme déclaratif est la expression. Les expressions n'ont pas d'effets secondaires. Ils spécifient une relation entre une entrée et une sortie, créant une sortie nouvelle et séparée de leur entrée, plutôt que de modifier leur état d'entrée. Une séquence d'expressions n'a pas de sens sans que certains contiennent une expression spécifiant la relation entre eux. Le programmeur spécifie les relations entre les données et le programme en déduit l'ordre d'exécuter le calcul de ces relations. C'est ce que les gens veulent dire en disant à l'ordinateur quoi à faire.
Les langues impératives ont des expressions, mais leurs moyens primaires de faire effectuer des déclarations sont des déclarations. De même, les langues déclaratives ont des expressions avec une sémantique similaire à celle des déclarations, telles que des séquences monad à l'intérieur de la notation de Haskell do
, mais à leur cœur, ils sont une grande expression. Cela permet aux débutants d'écrire du code très impératif, mais le véritable pouvoir de la langue vient lorsque vous échappez à ce paradigme.
La véritable caractéristique définissant qui sépare le déclarant de la programmation impérative est dans un style déclaratif que vous ne donnez pas d'instructions séquencées; Au niveau inférieur Oui, la CPU fonctionne de cette façon, mais c'est une préoccupation du compilateur.
Vous suggérez que CSS est une "langue déclarative", je ne l'appellerais pas du tout une langue. C'est un format de structure de données, comme JSON, XML, CSV ou INI, juste un format permettant de définir des données connues d'un interprète de la nature.
Certains effets secondaires intéressants se produisent lorsque vous prenez l'opérateur d'affectation d'une langue, ce qui est la cause réelle de perdre toutes ces instructions impératives Step1-Step2-Step3 dans les langues déclaratives.
Que faites-vous avec l'opérateur d'affectation dans une fonction? Vous créez des étapes intermédiaires. C'est le creux de celui-ci, l'opérateur d'affectation est utilisé pour modifier les données de manière pas-à-pas. Dès que vous ne pouvez plus modifier les données, vous perdez toutes ces étapes et vous retrouvez avec:
Chaque fonction n'a qu'une seule déclaration, cette affirmation étant la seule déclaration
Maintenant, il existe de nombreuses façons de faire une seule déclaration ressemblent à de nombreuses instructions, mais ce n'est que de tromperie, par exemple: 1 + 3 + (2*4) + 8 - (7 / (8*3))
Ceci est clairement une déclaration unique, mais si vous l'écrivez comme ...
1 +
3 +
(
2*4
) +
8 -
(
7 / (8*3)
)
Il peut prendre l'apparition d'une séquence d'opérations qui peuvent être plus faciles que le cerveau reconnaisse la décomposition souhaitée que l'auteur avait l'intention. Je fais souvent cela en C # avec du code comme ..
people
.Where(person => person.MiddleName != null)
.Select(person => person.MiddleName)
.Distinct();
Il s'agit d'une déclaration unique, mais montre l'apparition d'être décomposé en plusieurs préavis, il n'y a pas de missions.
Notez également, dans les deux méthodes ci-dessus - la manière dont le code est réellement exécuté n'est pas dans l'ordre que vous le lisez immédiatement; Parce que ce code dit quoi faire, mais cela ne dicte pas Comment. Notez que l'arithmétique simple ci-dessus ne va évidemment pas être exécutée dans la séquence qu'elle est écrite de gauche à droite, et dans l'exemple C # au-dessus de ces 3 méthodes sont réellement réalisatrices et non exécutées à l'achèvement de la séquence, le compilateur génère en effet du code. Cela fera ce que cette déclaration veut, mais pas nécessairement Comment Vous supposez.
Je crois que s'habituer à cette approche no-intermédiaire-étapes du codage déclaratif est vraiment la partie la plus difficile de tout; Et pourquoi Haskell est si délicat parce que peu de langues interdisent vraiment les choses comme Haskell. Vous devez commencer à faire des gymnastiques intéressantes pour accomplir certaines choses que vous feriez généralement avec l'aide de variables intermédiaires, telles que la somme.
Dans une langue déclarative, si vous voulez une variable intermédiaire de faire quelque chose avec - cela signifie le transmettre en tant que paramètre à une fonction qui fait cette chose. C'est pourquoi la récursion devient si importante.
sum xs = (head xs) + sum (tail xs)
Vous ne pouvez pas créer une variable resultSum
et l'ajouter au fur et à mesure que vous en boucle xs
, vous devez simplement prendre la première valeur et l'ajouter à la somme de tout le reste - et Pour accéder à tout le reste, vous devez passer xs
à une fonction tail
car vous ne pouvez pas simplement créer une variable pour XS et faire sauter la tête qui serait une approche impérative. (Oui, je sais que vous pouvez utiliser la déstructuration, mais cet exemple est censé être illustratif)
Je sais que je suis en retard à la fête, mais j'avais une épiphanie l'autre jour alors ça va ...
Je pense que les commentaires sur les effets secondaires fonctionnels, immuables et aucun effets secondaires ne manquent la marque lors de l'explication de la différence entre les déclaratifs ou expliquer quels moyens de programmation déclaratif. De plus, comme vous le mentionnez dans votre question, l'ensemble de "quoi faire" vs "Comment faire" est trop vague et n'explique vraiment pas non plus.
Prenons le code simple a = b + c
Comme base et affichez la déclaration dans quelques langues différentes pour obtenir l'idée:
Quand nous écrivons a = b + c
Dans une langue impérative, comme c, nous attribuons la valeur actuelle de b + c
à la variable a
et rien de plus. Nous ne faisons aucune déclaration fondamentale sur ce que a
est. Nous exécutons plutôt une étape dans un processus.
Quand nous écrivons a = b + c
Dans une langue déclarative, comme Microsoft Excel, (Oui, Excel est une langue de programmation et probablement le plus déclaratif de tous,) Nous sommes affirmant une relation entre a
, b
et c
tel qu'il est toujours le cas que a
est la somme des deux autres. Ce n'est pas une étape dans un processus, c'est un invariant, une garantie, une déclaration de vérité.
Les langues fonctionnelles sont également déclaratives, mais presque par accident. Dans Haskel par exemple a = b + c
affirme également une relation invariante, mais seulement parce que b
et c
sont immuables.
Donc, oui, lorsque les objets sont immuables et que les fonctions sont libres d'effet secondaire, le code devient déclaratif (même s'il a l'air identique au code impératif), mais ils ne sont pas le point. N'évite pas non plus l'affectation. Le point de code déclaratif est de faire des déclarations fondamentales sur les relations.
Vous avez raison qu'il n'y a pas de distinction claire entre dire à l'ordinateur quoi à faire et comment pour le faire.
Cependant, d'un côté du spectre, vous pensez presque exclusivement à la manipulation de la mémoire. C'est-à-dire que vous pouvez résoudre un problème, vous le présentez à l'ordinateur sous une forme similaire "Définissez cet emplacement de mémoire sur X, puis définissez cet emplacement de mémoire sur Y, saute à l'emplacement de la mémoire Z ...", puis vous finirez par avoir le résultat dans un autre emplacement de mémoire.
Dans les langues gérés comme Java, C #, etc., vous n'avez plus d'accès direct à la mémoire matérielle. Le programmeur impératif est désormais concerné par les variabes statiques, les références ou les champs d'instances de classe, qui sont toutes à des obstacles de degré pour les emplacements de mémoire.
À Languguga comme Haskell, OTOH, la mémoire est complètement parti. Ce n'est tout simplement pas le cas que dans
f a b = let y = sum [a..b] in y*y
il doit y avoir deux cellules de mémoire qui maintiennent les arguments A et B et un autre qui détiennent le résultat intermédiaire Y. Pour être sûr, un backend de compilateur pourrait émettre du code final qui fonctionne de cette façon (et en quelque sorte, il doit le faire tant que l'architecture cible est une machine v. Neumann).
Mais le but est que nous n'avons pas besoin d'internaliser la v. Neumann Architecture pour comprendre la fonction ci-dessus. Nous n'avons pas non plus besoin d'un ordinateur contemporain pour l'exécuter. Il serait facile de traduire des programmes dans un pure FP Langue à une machine hypothétique qui fonctionne sur la base du calcul de ski, par exemple. Essayez maintenant la même chose avec un programme C!
Une pure langues déclaratives pour moi serait une qui est entièrement composée de déclarations seulement
Ce n'est pas assez fort, imho. Même un programme C n'est qu'une séquence de déclarations. Je pense que nous devons plus éliguer les déclarations. Par exemple, nous disent-ils ce que quelque chose est (déclaratif) ou ce qu'il fait (impératif).