Lassen Sie uns darüber nachdenken, was deklarative Programmierung in Java und Elm ist (Teil 1).

Bewirken

Kennen Sie den Begriff deklarative Programmierung? Dies ist einer der Stile beim Programmieren. Werfen wir einen Blick auf Wikipedia. In vielen Fällen denke ich, dass das letztere konkrete Programmierparadigma oder die letztgenannte Sprache oft vorgestellt wird. Daher unterstützt dieser Artikel (wahrscheinlich) Java, das keine deklarative Programmierung unterstützt. Ich möchte einen Vergleich mit Elm anstellen. Elm ist eine Spezialsprache für die Web-Front-Programmierung. Was genau ist deklarative Programmierung? In Wikipedia erfolgt die Programmierung durch Angabe der Art des Ziels und nicht der Verarbeitungsmethode. Es gibt eine Beschreibung. Java, das das Ziel dieser Zeit sein wird, wird hauptsächlich unter Verwendung des prozeduralen Typs programmiert. Werfen wir einen Blick auf die Motivation dieses Artikels und verwenden dann den tatsächlichen Code als Beispiel, um ein Gefühl für den spezifischen Unterschied zu bekommen.

Deklarative Programmierung (Englisch: Deklarative Programmierung) ist der Name des Programmierparadigmas, hat jedoch zwei Hauptbedeutungen. Die erste Bedeutung ist ein Paradigma für die Programmierung, indem die Art des Objekts und nicht die Verarbeitungsmethode angegeben wird. Die zweite Bedeutung ist ein allgemeiner Begriff für reine funktionale Programmierung, logische Programmierung und Einschränkungsprogrammierung. Letzteres hat die erstere Natur (mehr oder weniger).

Diese Motivation ist für diejenigen, die normalerweise prozedurale Programmierung durchführen, sich an deklarative Programmierung zu gewöhnen. Ich hoffe, es wird eine Referenz für die Umstellung des Gehirns auf einen schrittweisen Übergang sein. Dieser Artikel ist in den ersten Teil und den zweiten Teil unterteilt, und der erste Teil besteht darin, den Code der deklarativen Programmierung schrittweise anhand eines einfachen Beispiels zu erstellen, das eine beliebige Stufe von 99 ausgibt. Abschließend werde ich den Code in Elm kurz erläutern. Im zweiten Teil möchte ich Techniken für die deklarative Programmierung vorstellen, indem ich speziell die Merkmale welcher Art von Sprache verwende.

Neunundneunzig Programm

Schreiben wir nun ein Programm, das eine beliebige Stufe von neunundneunzig ausgibt.

Geben Sie in den Spezifikationen dieses Programms eine beliebige Anzahl von Spalten an (3 Spalten werden zur Vereinfachung durch Variablen angegeben) und geben Sie sie in einer durch Leerzeichen halber Breite getrennten Zeile aus. Dies ist der Inhalt des Programms, das wir dieses Mal machen werden. Wenn Sie es selbst schreiben, ohne auf den Code zu achten, können Sie zunächst überprüfen, ob Sie prozedural oder instruktiv sind.

Der erste ist der Code, wenn das Problem so interpretiert und im klassischen Java-Stil geschrieben wird. Die printNMultiTable-Methode, die eine beliebige Spalte in einer Zeile ausgibt, ist definiert. Ich denke, dies kann in Worten wie folgt ausgedrückt werden.

  1. Deklarieren Sie die Zustandsvariable i und setzen Sie den Anfangswert auf 1.
  2. Wenn i nicht 9 ist, fügen Sie nach dem Ergebnis von i * n ein Leerzeichen mit halber Breite ein und geben Sie es in die Standardausgabe aus.
  3. Wenn i 9 ist, geben Sie i * n auf die Standardausgabe aus
  4. Fügen Sie der zu aktualisierenden Statusvariablen 1 hinzu und wiederholen Sie den obigen Vorgang (2, 3), bis i 9 wird.
  5. Wenn die Iteration beendet ist, geben Sie einen Zeilenumbruch an die Standardausgabe aus und beenden Sie die Methode.

Dieser Code ist eine prozedurale Programmierung auf folgende Weise:

Mit anderen Worten, der Inhalt, den der Computer verarbeitet, und der Inhalt, der die neunundneunzig berechnet, werden gemischt.

 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();
    }
  }  

Bevor ich zum nächsten Schritt übergehe, möchte ich die Vor- und Nachteile des obigen Verfahrensstils betrachten. Der Prozedurtyp ist ein Code, der deutlich zeigt, dass das berechnete Ergebnis an die Standardausgabe ausgegeben wird, die das endgültige Ziel darstellt. Dies führt zu einer guten Leistung, wenn es gut gemacht wird. Was ist andererseits diese Speicherplatzausgabe, wenn jemand, der die Spezifikationen nicht kennt, diesen Code sieht? Was ist der letzte Zeilenumbruch? Muss gefolgert werden. Das Schreiben von Testcode ist ein besserer Weg, um Rückschlüsse zu vermeiden und Spezifikationen zu hinterlassen. Der Testcode des standardmäßig ausgegebenen Ergebnisses ist jedoch nicht einfach. Die Kosten sind es nicht wert, da hierfür Anstrengungen erforderlich sind, die vom ursprünglichen Zweck abweichen, z. B. die visuelle Überprüfung mit einem Debugger, die Ausgabe in eine Datei und die Erstellung eines Diff der Datei. Löst die deklarative Programmierung diese Probleme? Jetzt möchte ich mit dem nächsten Schritt fortfahren.

Im nächsten Schritt konzentrieren wir uns auf das Ergebnis der Berechnung und nicht auf das endgültige Ziel. Insbesondere ist es in zwei Methoden unterteilt: die Methode nMultiTable, die List zurückgibt, die die dritte Stufe darstellt, und die printList-Methode, die den Inhalt der Liste durch Leerzeichen getrennt ausgibt. Auf diese Weise können Sie Testcode schreiben, der besagt, dass ein Methodenaufruf mit dem Namen nMultiTable (3) ein Ausdruck ist und das ausgewertete Ergebnis einer Liste entspricht, die die 3. Stufe darstellt. Das Problem bei diesem Schritt besteht darin, dass der prozedurale Teil in der Methode selbst verbleibt, was die Code-Inferenz immer noch schwierig macht.


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();
   }
 }
 

Lassen Sie uns den Code von nMultiTable verbessern. Die Zustandsvariable i zum Drehen der for-Schleife erzeugte eine Folge von Zahlen basierend auf neunundneunzig. Die einfachste Lösung, um diese Erklärung abzugeben, besteht darin, die Zahlen von 1 bis 9 direkt aufzulisten.

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();
   }
 }

Die manuelle Aufzählung war jedoch eine zu aggressive Lösung. Daher habe ich eine Methode namens IntStream # rangeClosed verwendet, um einen Stream im Bereich zu erstellen und ihn mit collect in eine Liste zu konvertieren. Was ist der Unterschied zwischen diesem und dem ersten? Diesmal war es sehr einfach, daher gibt es keinen großen Unterschied, aber da diese Basis keine Neufassung des Staates, sondern eine konkrete Liste ist, kann dies auch als Testziel betrachtet werden. Da die Methode garantiert, dass eine Liste von 1 bis 9 generiert wird, ist es auch möglich, Fehler wie Inkrementfehler zu verhindern, indem der Fehler, für den i neu geschrieben wird, durch den erweiterten für ersetzt wird.

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();
   }
 }

Früher habe ich die Basisnummernfolge mit der Anzahl der neunundneunzig Spalten multipliziert, aber es ist nicht immer notwendig, die Zahlenfolge beizubehalten. Rufen Sie die IntStream # mapToObj-Methode auf, um sie in einen Stream zu konvertieren, der das Ergebnis der Multiplikation aller Elemente der Basisnummernspalte ist. Da es sich nicht um einen Einführungsartikel zu Stream handelt, finden Sie die spezifische Bedeutung in der Referenz usw.

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();
   }
 }

Nachdem wir dies in einer Zeile getan haben, löschen wir die nMultiTable-Methode.

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();
   }
 }

Schauen wir uns als nächstes die printList an. Standardausgabe des Inhalts der Liste, getrennt durch Leerzeichen halber Breite. Wenn Sie diesen Prozess deklarativ betrachten, können Sie sagen, dass eine Zeichenfolge in eine Zeichenfolge und dann in eine durch die halbe Breite getrennte Zeichenfolge konvertiert werden soll. String # join ist eine Methode, mit der die Liste durch einen angegebenen String getrennt wird und die zum Anpassen geeignet ist. Verwenden Sie printableList anstelle von printList. Beim Aufruf von mapToObj sollte die zuvor erwähnte threeTable ebenfalls in eine Zeichenfolge konvertiert werden.

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();
   }
 }

Wenn Sie sorgfältig darüber nachdenken, können Sie es anscheinend in eine Zeichenfolge konvertieren, wenn Sie es mit der collect-Methode beenden. Nennen wir die Join-Methode Collectors #. Ich konnte es in nur einer Zeile in prozeduralen Code konvertieren!

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();
   }
 }

Aber warte. Übermäßige Codeauslassungen reduzieren den Testcode, was zu einer allgemeinen Codesicherheit und umgekehrt zu einer besseren Lesbarkeit führen kann. Beachten Sie bei der praktischen Programmierung dieses Gleichgewicht beim Refactoring. Geben Sie die nMultiTable-Methode zurück, damit Sie Testcode für eine beliebige Anzahl von Spalten schreiben können. Auf der anderen Seite werden die durch Leerzeichen getrennten Zeichenfolgen nur für die endgültige Ausgabe verarbeitet, sodass wir entschieden haben, dass keine Tests erforderlich sind. Dieses Urteil ist nicht die einzig richtige Antwort. Lassen Sie uns unter Berücksichtigung der Implementierungsanforderungen entwerfen.

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();
   }
 }

Wie ich im vorherigen Schritt festgestellt habe, == deklarative Programmierung == nicht mit StreamAPI programmieren. Auch deklarative Programmierung ist keine gute Technik. Es gibt viele Fälle, in denen es manchmal besser ist, prozedurale Programmierung durchzuführen. Insbesondere ist Java ursprünglich eine Sprache, die prozedurbasiertes OOP unterstützt. Daher gibt es viele Fälle, in denen hervorragende prozedurale Methoden bereitgestellt werden. Gehen wir daher zur deklarativen Programmierung über, während wir auf das Gleichgewicht achten, damit das Mittel nicht der Zweck ist.

Deklarative Programmierung mit Elm

Schauen wir uns das Elm-Beispiel an, das hauptsächlich deklarative Programmierung unterstützt. Eine große Waffe, wenn Sie iterieren möchten, ist ein rekursiver Aufruf. let definiert den Ausdruck, der benötigt wird, um den Wert endgültig zurückzugeben. In in beschreibt einen Ausdruck, der den Endwert zurückgibt. Diese Formel stellt die Eigenschaft selbst dar und ist das Ergebnis der durch den Wert dargestellten Eigenschaft. Im zweiten Teil werde ich verschiedene Elm-Funktionen vorstellen, aber in einfachen Worten sind alle Formeln. Ausdrücke geben im Gegensatz zu Anweisungen immer einen Wert zurück. Sie müssen also alles genau schreiben, nicht einen mehrdeutigen Ausdruck, der ein Ergebnis zurückgeben könnte. Im folgenden Beispiel ist die IF ein Ausdruck (die else-Klausel kann nicht weggelassen werden, da sie streng ist). Der Inhalt von if ist wie folgt.

Tabelle (gibt Liste zurück), wenn x 10 ist. Dies ist das Endergebnis Andernfalls beginnt die Liste rekursiv mit n * x (n ist die Anzahl der Spalten und x ist das Element der Basiszahlspalte) und das letztere Element ist x + 1.

Mit anderen Worten, im Fall von 3 Schritten

(3 * 1) :: (3 * 2) :: (3 * 3) ... (3 * 9) :: [] == [3, 6, 9, ..., 27]

Es wird sein.

In in beginnt x bei 1 und ist zunächst eine leere Liste, daher ein expliziter Ausdruck. In threeTableText wird die Liste mit 3 Spalten in eine Liste mit Zeichenfolgen und in eine durch Leerzeichen getrennte Zeichenfolge konvertiert. Dies ist das gleiche wie im Java-Beispiel.

 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

Natürlich können Sie dasselbe tun wie die Stream-API, und der endgültige Code hat fast die gleiche Bedeutung wie der endgültige Java-Code. Im Gegensatz zu Java enthält die Standardliste jedoch nützliche Methoden für viele deklarative Programmierungen, ohne dass eine spezielle Datenstruktur wie Stream aufgerufen werden muss. Daher ist der Code sehr einfach.


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

Lassen Sie uns darüber nachdenken, was deklarative Programmierung in Java und Elm ist (Teil 1).
Was ich in Java gelernt habe (Teil 4) Bedingte Verzweigung und Wiederholung
Denken Sie über die Unterschiede zwischen Funktionen und Methoden nach (in Java)
Was ist ein Ausschnitt in der Programmierung?
Was ich in Java gelernt habe (Teil 1) Java-Entwicklungsablauf und Überblick
Was ist eine Klasse in der Java-Sprache (3 /?)
Verwenden Sie OpenCV_Contrib (ArUco) mit Java! (Teil 2-Programmierung)
Was ist eine Klasse in der Java-Sprache (1 /?)
Was ist Java und Entwicklungsumgebung (MAC)
Was ist eine Klasse in der Java-Sprache (2 /?)
Was ist die Hauptmethode in Java?
Was ist java
Was ist Java <>?
Was ich in Java gelernt habe (Teil 2) Was sind Variablen?
Was ist java
[LeJOS] Programmieren wir mindstorm-EV3 mit Java [Umgebungskonstruktion Teil 2]
[JAVA] Was ist der Unterschied zwischen Schnittstelle und Zusammenfassung? ?? ??
Was ich in Java gelernt habe (Teil 3) Anweisung zur Ausführung von Anweisungen
[# 2 Java] Welche Objektorientierung hören Sie oft?
Denken Sie an das JAVA = JAVAscript-Problem (wird in Zukunft benötigt).
Einschränkungsprogrammierung in Java
Was ist Java-Kapselung?
Was ist Java-Technologie?
Was ist Java API-Java?
[Java] Was ist flatMap?
[Java] Was ist ArrayList?
JSON in Java und Jackson Teil 1 Gibt JSON vom Server zurück
[LeJOS] Programmieren wir mindstorm-EV3 mit Java [Umgebungskonstruktion erster Teil]
Was ist die LocalDateTime-Klasse? [Java-Anfänger] -Datum und Zeitklasse-
Was ist in "Java 8 bis Java 11" passiert und wie wird eine Umgebung erstellt?
StringUtils.isNotBlank eignet sich für Leerurteile und Nullurteile in Java
Road to Java Engineer Part2 Was für eine Sprache ist Java?
AtCoder dwango Programmierwettbewerb B zum Lösen in Ruby, Perl und Java B.
Ist die Kurzschlussauswertung wirklich schnell? Unterschied zwischen && und & in Java
Implementieren wir die Bedingung, dass der Umfang und das Innere der Ougi-Form in Java enthalten sind [Teil 2]
Implementieren wir die Bedingung, dass der Umfang und das Innere der Ougi-Form in Java enthalten sind [Teil 1]
Was ist Java Assertion? Zusammenfassung.
Was ich über Java 8 recherchiert habe
Was ich über Java 6 recherchiert habe
Erstaunliche Java-Programmierung (hören wir auf)
Java und Iterator Teil 1 Externe Iterator Edition
Was ich über Java 9 recherchiert habe
Apache Hadoop und Java 9 (Teil 1)
[Java] Über String und StringBuilder
Was ist eine Java-Sammlung?
Welche Revolution passiert in der Programmierung
Was ich über Java 7 recherchiert habe
[Java] Was ist jaee j2ee?
[Java] Was ist Klassenvererbung?
Über Java-Paket und Import
Über Java Abstract Class
[Java-Grundlagen] Was ist Klasse?
Was ist Java-Fluchtanalyse?
Was sind Microservices und Microservices Frameworks?
Was ich über Java 5 recherchiert habe