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.
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.
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
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.
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