Connaissez-vous le terme programmation déclarative? C'est l'un des styles lors de la programmation. Jetons un coup d'œil à Wikipedia. Dans de nombreux cas, je pense que ce dernier paradigme ou langage de programmation concret est souvent imaginé, donc cet article prend (probablement) en charge Java, qui ne prend pas en charge la programmation déclarative. Je voudrais faire une comparaison avec Elm. Elm est un langage spécialisé pour la programmation frontale Web. Alors, quelle est exactement la programmation déclarative? Dans Wikipedia, la programmation se fait en déclarant la nature de la cible et non la méthode de traitement. Il y a une description. Java, qui sera la cible de cette fois, sera programmé principalement en utilisant le type procédural. Jetons un coup d'œil à la motivation de cet article, puis utilisons le code réel comme exemple pour avoir une idée de la différence spécifique.
La programmation déclarative (anglais: programmation déclarative) est le nom du paradigme de programmation, mais elle a deux significations principales. La première signification est un paradigme pour la programmation en déclarant la nature de l'objet, pas la méthode de traitement. La deuxième signification est un terme général pour la programmation fonctionnelle pure, la programmation logique et la programmation par contraintes. Ce dernier a la première nature (plus ou moins).
Cette motivation est pour ceux qui font habituellement de la programmation procédurale de s'habituer à la programmation déclarative. J'espère que ce sera une référence pour changer le cerveau pour une transition progressive. Cet article est divisé en la première partie et la deuxième partie, et la première partie consiste à créer progressivement le code de programmation déclarative à travers un exemple simple qui génère n'importe quelle étape de 99. Enfin, je vais expliquer brièvement le code dans Elm. Dans la deuxième partie, je voudrais présenter des techniques de programmation déclarative en utilisant spécifiquement les caractéristiques de quel type de langage.
Maintenant, écrivons un programme qui produit n'importe quelle étape de quatre-vingt-dix-neuf.
Dans les spécifications de ce programme, spécifiez un nombre quelconque de colonnes (3 colonnes sont spécifiées par des variables pour simplification), et la sortie sur une ligne séparée par des espaces demi-largeur. C'est le contenu du programme que nous allons faire cette fois. Tout d'abord, si vous l'écrivez vous-même sans regarder le code, vous pouvez vérifier si vous êtes procédural ou pédagogique.
Le premier est le code lorsque le problème est interprété tel quel et écrit dans le style Java classique. Il définit la méthode printNMultiTable, une fonction qui génère une colonne arbitraire sur une ligne. Je pense que cela peut être exprimé en mots comme suit.
Ce code est une programmation procédurale des manières suivantes:
En d'autres termes, le contenu que l'ordinateur traite et le contenu qui calcule les quatre-vingt-dix-neuf sont mélangés.
public class Test {
public void printNMultiTable(int n) {
for (int i = 1; i <= 9; i++) {
if (i != 9) {
System.out.print(i * n + " ");
} else {
System.out.print(i * n);
}
}
System.out.println();
}
public Test() {
printNMultiTable(3);
// => 3 6 9 12 15 18 21 24 27
}
public static void main(String args[]) {
new Test();
}
}
Avant de passer à l'étape suivante, j'aimerais examiner les avantages et les inconvénients du style procédural ci-dessus. Le type procédural est un code qui montre clairement que le résultat calculé est sorti vers la sortie standard, qui est l'objectif final. Cela se traduira par de bonnes performances s'il est bien fait. D'autre part, quelle est cette sortie d'espace quand quelqu'un qui ne connaît pas les spécifications voit ce code? Quel est le dernier saut de ligne? Doit être déduit. L'écriture de code de test est un meilleur moyen d'éviter les inférences et de laisser des spécifications. Cependant, le code de test du résultat qui est sorti en standard n'est pas facile. Cela ne vaut pas le coût car cela nécessite des efforts qui s'écartent de l'objectif initial, tels que la vérification visuelle à l'aide d'un débogueur, la sortie dans un fichier et la prise d'un diff du fichier. La programmation déclarative résout-elle ces problèmes? Je voudrais maintenant passer à l’étape suivante.
Dans l'étape suivante, nous nous concentrerons sur le résultat du calcul, pas sur l'objectif final. Plus précisément, elle est divisée en deux méthodes: la méthode nMultiTable qui renvoie List
import java.util.List;
import java.util.ArrayList;
public class Test {
public List<Integer> nMultiTable(int n) {
final List<Integer> table = new ArrayList<>();
for (int i = 1; i <= 9; i++) {
table.add(i * n);
}
return table;
}
public void printList(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
System.out.print(list.get(i) + " ");
} else {
System.out.print(list.get(i));
}
}
System.out.println();
}
public Test() {
List<Integer> threeTable = nMultiTable(3);
// == Arrays.asList(3, 6, 9, 12, 15, 18, 21, 24, 27)
printList(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
Améliorons le code de nMultiTable. La variable d'état i pour tourner la boucle for a généré une chaîne de nombres basée sur quatre-vingt-dix-neuf. La solution la plus simple pour rendre ce déclaratif est de lister directement les nombres de 1 à 9.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class Test {
public List<Integer> nMultiTable(int n) {
final List<Integer> table = new ArrayList<>();
final List<Integer> base = Arrays.asList(1, 2, 3, 4, 5, 5, 6 ,7, 8, 9);
for (int x : base) {
table.add(x * n);
}
return table;
}
public void printList(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
System.out.print(list.get(i) + " ");
} else {
System.out.print(list.get(i));
}
}
System.out.println();
}
public Test() {
List<Integer> threeTable = nMultiTable(3);
printList(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
Cependant, l'énumération à la main était une solution trop agressive, j'ai donc utilisé une méthode appelée IntStream # rangeClosed pour créer un flux dans la plage et le convertir en une liste avec collect. Quelle est la différence entre ceci et tourner le premier pour? Cette fois, c'était très simple, donc il n'y a pas de grande différence, mais comme cette base n'est pas une réécriture de l'état mais une liste concrète, cela peut aussi être considéré comme une cible de test. De plus, étant donné que la méthode garantit qu'une liste de 1 à 9 est générée, il est possible d'éviter des erreurs telles que des erreurs d'incrémentation en remplaçant celle dans laquelle pour réécrit i par l'extension pour.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public List<Integer> nMultiTable(int n) {
final List<Integer> table = new ArrayList<>();
final List<Integer> base = IntStream.rangeClosed(1, 9).boxed().collect(Collectors.toList());
for (int x : base) {
table.add(x * n);
}
return table;
}
public void printList(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
System.out.print(list.get(i) + " ");
} else {
System.out.print(list.get(i));
}
}
System.out.println();
}
public Test() {
List<Integer> threeTable = nMultiTable(3);
printList(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
J'avais l'habitude de multiplier la séquence numérique de base par le nombre de quatre-vingt-dix-neuf colonnes, mais il n'est pas toujours nécessaire de conserver la séquence numérique. Appelez la méthode IntStream # mapToObj pour le convertir en un Stream qui est le résultat de la multiplication de tous les éléments de la colonne numérique de base. Puisqu'il ne s'agit pas d'un article d'introduction sur Stream, veuillez vous référer à la référence etc. pour la signification spécifique.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public List<Integer> nMultiTable(int n) {
return IntStream.rangeClosed(1, 9).mapToObj(x -> x * n).collect(Collectors.toList());
}
public void printList(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
System.out.print(list.get(i) + " ");
} else {
System.out.print(list.get(i));
}
}
System.out.println();
}
public Test() {
List<Integer> threeTable = nMultiTable(3);
printList(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
Maintenant que nous l'avons fait en une seule ligne, supprimons la méthode nMultiTable.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public void printList(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
System.out.print(list.get(i) + " ");
} else {
System.out.print(list.get(i));
}
}
System.out.println();
}
public Test() {
int n = 3;
List<Integer> threeTable = IntStream.rangeClosed(1, 9).mapToObj(x -> x * n).collect(Collectors.toList());
printList(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
Ensuite, regardons la printList. Sortie standard du contenu de List séparé par des espaces demi-largeur. Si vous pensez à ce processus de manière déclarative, on peut dire que c'est pour convertir une chaîne de nombres en une chaîne de chaînes, puis en une chaîne séparée par demi-largeur. String # join est une méthode qui transforme la liste en une chaîne séparée par une chaîne spécifiée et convient à la personnalisation. Utilisez printableList au lieu de printList. Lors de l'appel de mapToObj, le threeTable mentionné précédemment doit également être converti en chaîne de caractères.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public String printableList(List<String> list) {
return String.join(" ", list);
}
public Test() {
int n = 3;
List<String> threeTable = IntStream.rangeClosed(1, 9).mapToObj(x -> String.valueOf(x * n)).collect(Collectors.toList());
System.out.println(printableList(threeTable));
}
public static void main(String args[]) {
new Test();
}
}
Si vous y réfléchissez bien, il semble que vous puissiez la convertir en chaîne de caractères lorsque vous la terminez avec la méthode collect. Appelons la méthode de jonction Collectors #. J'ai pu le convertir en code procédural en une seule ligne!
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public Test() {
int n = 3;
String threeTable = IntStream.rangeClosed(1, 9).mapToObj(x -> String.valueOf(x * n)).collect(Collectors.joining(" "));
System.out.println(threeTable);
}
public static void main(String args[]) {
new Test();
}
}
Mais attendez. Des omissions de code excessives réduisent le code de test, ce qui entraîne la sécurité globale et inversement la lisibilité du code. Dans la programmation pratique, gardez cet équilibre à l'esprit lors de la refactorisation. Renvoyez la méthode nMultiTable afin que vous puissiez écrire du code de test pour n'importe quel nombre de colonnes. D'un autre côté, les chaînes séparées par des espaces sont juste traitées pour la sortie finale, nous avons donc décidé qu'aucun test n'était nécessaire. Ce jugement n'est pas la seule bonne réponse. Concevons en tenant compte des exigences de mise en œuvre.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class Test {
public List<Integer> nMultiTable(int n) {
return IntStream.rangeClosed(1, 9).mapToObj(x -> x * n).collect(Collectors.toList());
}
public Test() {
final int n = 3;
final List<Integer> threeTable = nMultiTable(3);
// == Arrays.asList(3, 6, 9, 12, 15, 18, 21, 24, 27)
String threeTableText = threeTable.stream().map(String::valueOf).collect(Collectors.joining(" "));
// == "3 6 9 12 15 18 21 24 27"
System.out.println(threeTableText);
}
public static void main(String args[]) {
new Test();
}
}
Comme je l'ai noté à l'étape précédente, la programmation déclarative == ne programme pas avec StreamAPI. De plus, la programmation déclarative n'est pas une bonne technique. Il existe de nombreux cas où il est parfois préférable de faire de la programmation procédurale. En particulier, Java est à l'origine un langage qui prend en charge la POO basée sur des procédures, il existe donc de nombreux cas où d'excellentes méthodes procédurales sont fournies. Par conséquent, passons à la programmation déclarative en faisant attention à l'équilibre pour que les moyens ne soient pas le but.
Jetons un coup d'œil à l'exemple Elm, qui prend principalement en charge la programmation déclarative. Une grande arme si vous voulez itérer est un appel récursif. let définit l'expression nécessaire pour finalement renvoyer la valeur. In in décrit une expression pour renvoyer la valeur finale. Cette formule représente la propriété elle-même et est le résultat de la propriété représentée par la valeur. Dans la deuxième partie, je présenterai diverses fonctions Elm, mais en termes simples, toutes sont des formules. Les expressions, contrairement aux instructions, renvoient toujours une valeur. Vous devez donc tout écrire exactement, pas une expression ambiguë qui pourrait renvoyer un résultat. Dans l'exemple ci-dessous, le IF est une expression (la clause else ne peut pas être omise car elle est stricte). Le contenu de if est le suivant.
table (renvoie List) lorsque x vaut 10. Cela représente le résultat final Sinon, la liste commence récursivement par n * x (n est le nombre de colonnes et x est l'élément de la colonne numérique de base) et ce dernier élément est x + 1.
En d'autres termes, dans le cas de 3 étapes
(3 * 1) :: (3 * 2) :: (3 * 3) ... (3 * 9) :: [] == [3, 6, 9, ..., 27]
Ce sera.
Dans in, x commence à 1 et est d'abord une liste vide, c'est donc une expression explicite. Dans threeTableText, la liste à trois colonnes est convertie en une liste de chaînes et convertie en une chaîne séparée par des espaces. C'est le même que l'exemple Java.
module Test exposing (..)
import Html exposing (text)
nMultiTable : Int -> List Int
nMultiTable n =
let
nMulti x table =
if x == 10 then
table
else
(n * x) :: nMulti (x + 1) table
in
nMulti 1 []
main =
let
n =
3
threeTable =
nMultiTable n
threeTableText =
threeTable |> List.map toString |> String.join " "
in
text threeTableText
Bien sûr, vous pouvez faire la même chose que l'API Stream, et le code final a presque la même signification que le code Java final. Cependant, contrairement à Java, la liste par défaut contient des méthodes utiles pour faire beaucoup de programmation déclarative sans avoir à afficher une structure de données spéciale comme Stream. Par conséquent, le code est très simple.
module Test exposing (..)
import Html exposing (text)
nMultiTable : Int -> List Int
nMultiTable n =
List.range 1 9 |> List.map (\x -> x * n)
main =
let
n =
3
threeTable =
nMultiTable n
threeTableText =
threeTable |> List.map toString |> String.join " "
in
text threeTableText
Recommended Posts