Let's think about what declarative programming is in Java and Elm (Part 1)

Effect

Do you know the term declarative programming? This is one of the styles of programming. Let's take a look at Wikipedia. In many cases, I think that the latter concrete programming paradigm or language is often imagined, so this article (probably) supports Java, which does not support declarative programming. I would like to make a comparison with Elm. Elm is a specialized language for web front programming. So what exactly is declarative programming? In Wikipedia, programming is done by declaring the nature of the target, not the processing method. There is a description. Java, which will be the target of this time, will be programmed mainly using the procedural type. Let's take a look at the motivation of this article and then use the actual code as an example to get a feel for the specific difference.

Declarative programming (English: Declarative programming) is the name of a programming paradigm, but it has two main meanings. The first meaning is a paradigm of programming by declaring the nature of the object, not the processing method. The second meaning is a general term for pure functional programming, logic programming, and constraint programming. The latter has the former nature (more or less).

This motivation is for those who usually do procedural programming to get used to declarative programming. I hope it will be a reference for switching the brain for gradual transition. This article is divided into a first part and a second part, and the first part is to gradually make declarative programming code through a simple sample that outputs any stage of multiplication table. Finally, I will briefly explain the code in Elm and conclude. In the second part, I would like to introduce techniques for declarative programming by specifically utilizing the characteristics of what language.

Multiplication table program

Now, let's write a program that outputs any stage of multiplication tables.

In the specifications of this program, specify any number of columns (specify 3 columns by variable for simplification), and output to one line separated by half-width spaces. This is the content of the program we will make this time. If you write it yourself without looking at the code, you can see if you are procedural or imperative.

The first is the code that interprets the problem as is and writes it in the classical Java style. The printNMultiTable method, which outputs an arbitrary column to one line, is defined. I think this can be expressed in words as follows.

  1. Declare the state (counter) variable i and set the initial value to 1.
  2. If i is not 9, insert a half-width space after the result of i * n and output to standard output.
  3. If i is 9, output i * n to standard output
  4. Add 1 to the state variable to update, and repeat the above process (2, 3) until i becomes 9.
  5. When the iteration is finished, output a newline to the standard output and exit the method.

This code is procedural programming in the following ways:

--The iteration is processed by rewriting the variable i. In other words, I am conscious of the rewriting process of the memory of the PC --I am conscious of outputting the result to standard output

In other words, the content processed by the computer and the content calculated by the multiplication table are mixed.

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

Before moving on to the next step, I would like to consider the advantages and disadvantages of the above procedural style. The procedural type is a code that clearly shows that the calculated result is output to the standard output, which is the final goal. This will result in good performance if done well. On the other hand, what is this space output when someone who doesn't know the spec sees this code? What is the last line break? Must be inferred. Writing test code is a better way to prevent inference and leave specifications. However, the test code of the result that is output as standard is not easy. It is not worth the cost because it requires efforts that deviate from the original purpose, such as visually checking using a debugger, outputting to a file and diffing the file. Does declarative programming solve these problems? Now I would like to move on to the next step.

In the next step, we will focus on the result of the calculation, not the final goal. Specifically, it is divided into two methods: the method nMultiTable that returns List that represents the third stage, and the printList method that outputs the contents of the List separated by spaces. By doing this, you can write test code that the method call called nMultiTable (3) is an expression and the evaluated result is equal to the List representing the 3rd stage. The problem with this step is that the procedural part remains in the method itself, making code inference still difficult.


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

Let's improve the code of nMultiTable. The state variable i for turning the for loop generated a sequence of numbers based on multiplication tables. The most straightforward solution to make this declarative is to list the numbers from 1-9 directly.

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

However, enumerating by hand was a too aggressive solution, so I used a method called IntStream # rangeClosed to create a stream in the range and convert it to a List with collect. What is the difference between this and turning the first for? This time it was a very simple content, so there is no big difference, but since this base has become a concrete List instead of rewriting the state, this can also be considered as a test target. Also, since the method guarantees that a List of 1-9 is generated, it is possible to prevent mistakes such as increment mistakes by replacing the one in which for rewrites i with the extended for.

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

I used to multiply the base sequence by the number of multiplication tables, but it is not always necessary to keep the sequence. Call the IntStream # mapToObj method to convert it to a Stream that is the result of multiplying all the elements of the base sequence. Since it is not an introductory article about Stream, please refer to the reference etc. for the specific meaning.

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

Now that we've done it in one line, let's delete the nMultiTable method.

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

Next, let's look at the printList. Standard output of the contents of List separated by half-width spaces. If you think of this process declaratively, you can say that it is to convert a string of numbers into a string of strings and then convert it to a string separated by half-width characters. String # join is a method that makes the list a string separated by the specified string and is suitable for customizing. Use printableList instead of printList. When calling mapToObj, the threeTable mentioned earlier should also be converted to a string.

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

If you think about it carefully, it seems that you can convert it to a string when terminating with the collect method. Let's call the Collectors # joining method. I was able to convert it to procedural code in just one line!

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

But wait. Excessive code omissions reduce test code, which can result in overall code safety and, conversely, readability. In practical programming, keep this balance in mind when refactoring. Return the nMultiTable method so that you can write test code for any sequence of columns. On the other hand, the space-separated strings are just processed for the final output, so we decided that no testing was necessary. This judgment is not the only correct answer. To the last, let's design while considering the implementation requirements.

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

As I noted in the previous step, declarative programming == not programming with StreamAPI. Also, declarative programming is not a good idea. There are many cases where it is sometimes better to do procedural programming. In particular, Java is originally a language that supports procedural-based OOP, so there are many cases where excellent procedural methods are provided. Therefore, let's move to declarative programming while paying attention to the balance so that the means is not the purpose.

Declarative programming with Elm

Let's take a look at the Elm example, which primarily supports declarative programming. A big weapon if you want to iterate is a recursive call. let defines the expression needed to finally return the value. In in describes an expression to return the final value. This formula represents the property itself and is the result of the property represented by the value. In the second part, I will introduce various Elm functions, but in simple terms, all of them are formulas. Expressions, unlike statements, always return a value. So you have to write everything exactly, not an ambiguous expression that might return a result. In the example below, the IF is an expression (the else clause cannot be omitted because it is strict). The contents of if are as follows.

table (returns a List) when x is 10. This represents the final result Otherwise, the list starts recursively with n * x (n is the number of columns and x is the element of the base sequence) and the following elements are x + 1.

In other words, in the case of 3 steps

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

It will be.

In in, x starts at 1 and is an empty list at first, so it's an explicit expression. In threeTableText, the three-column list is converted to a string list and converted to a space-separated string. This is the same as the Java example.

 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

Of course, you can do the same thing as the Stream API, and the final code has almost the same meaning as the final Java code. However, unlike Java, the default list contains useful methods for doing a lot of declarative programming without having to bring up special data structures like Stream. As a result, the code is very 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

Let's think about what declarative programming is in Java and Elm (Part 1)
What I learned in Java (Part 4) Conditional branching and repetition
Think about the differences between functions and methods (in Java)
What is a snippet in programming?
What I have learned in Java (Part 1) Java development flow and overview
What is a class in Java language (3 /?)
Use OpenCV_Contrib (ArUco) in Java! (Part 2-Programming)
What is a class in Java language (1 /?)
What is Java and Development Environment (MAC)
What is a class in Java language (2 /?)
What is the main method in Java?
What is java
What is Java <>?
What I learned in Java (Part 2) What are variables?
What is Java
What is programming?
[LeJOS] Let's program mindstorm-EV3 in Java [Environment construction part 2]
[JAVA] What is the difference between interface and abstract? ?? ??
What I learned in Java (Part 3) Instruction execution statement
[# 2 Java] What is object-oriented programming that you often hear?
Think about the JAVA = JAVAscript problem (needed in the future)
What is the difference between Java EE and Jakarta EE?
Constraint programming in Java
What is Java Encapsulation?
What is Java technology?
What is Java API-java
All about Java programming
[Java] What is flatMap?
[Java] What is JavaBeans?
[Java] What is ArrayList?
JSON in Java and Jackson Part 1 Return JSON from the server
[LeJOS] Let's program mindstorm-EV3 in Java [Environment construction first part]
What is the LocalDateTime class? [Java beginner] -Date and time class-
Jersey --What is Difference Between bind and bindAsContract in HK2?
What happened in "Java 8 to Java 11" and how to build an environment
StringUtils.isNotBlank is convenient for empty judgment and null judgment in java
Road to Java Engineer Part2 What kind of language is Java?
AtCoder dwango Programming Contest B in Ruby, Perl and Java
Is short-circuit evaluation really fast? Difference between && and & in Java
Let's implement the condition that the circumference and the inside of the Ougi shape are included in Java [Part 2]
Let's implement the condition that the circumference and the inside of the Ougi shape are included in Java [Part 1]
What is Java Assertion? Summary.
What I researched about Java 8
What is object-oriented programming? ~ Beginners ~
What I researched about Java 6
Amazing Java programming (let's stop)
Java and Iterator Part 1 External Iterator
What I researched about Java 9
Apache Hadoop and Java 9 (Part 1)
[Java] About String and StringBuilder
What is a Java collection?
What revolution happen in programming
What I researched about Java 7
[Java] What is jaee j2ee?
[Java] What is class inheritance?
About Java Packages and imports
About abstract classes in java
[Java basics] What is Class?
What is java escape analysis?
What is Microservices? And Microservices Frameworks
What I researched about Java 5