At first, I summarized object-oriented programming and its features, but I couldn't do it well and gave up.
So, instead of that, I thought it would be better to create a simple task, program it concretely, and compare it, so I decided to start with an easy addition program.
As the language, I chose Pascal (standard Pascal) as the procedure-oriented language, Java as the object-oriented language, and Haskell as the functional language.
This language choice is completely a hobby. I can't say it well, but I like a language that has a single paradigm and static types, that tends to have a long syntax, and that feels a bit "squishy".
Addition does not just calculate in one line, but accepts input from the user, and defines the function as follows.
1.First, "Please enter a number. It ends with a blank line." Is displayed.
2. 「>Is displayed and waiting for input. If you enter a number and start a new line, it will be recorded.
3.again">Is displayed and waiting for input. Repeat until a line break, without entering any characters.
4.If a blank line break occurs, the result of adding the sum of all the numbers entered so far is displayed. "The total is xxx" is displayed.
5.Ignore inputs that include characters other than numbers.
Actually, I think that a slightly more complicated sample will bring out the individuality of each language, but the first step is to do so.
The actual operation image is like this.
Please enter the number. It ends with a blank line.
>10
>20
>4
>
The total is 34.
This article is intended for beginners who have learned one programming language and can understand conditional branching and loop processing.
Let's consider standard Pascal here. Standard Pascal has few features and can't be compiled separately, but I think it's still suitable for learning programming, especially the basics of learning algorithms.
First, consider implementing the main routine as follows:
program CalcSumApp;
type
ItemPtr = ^Item;
Item =
record
next: ItemPtr;
text: ShortString;
end;
//Definition of various functions
var
lines : ItemPtr;
sum : Integer;
begin
writeln('Please enter the number. It ends with a blank line.');
lines := readLines();
sum := sumOfLines(lines);
writeln('The total is' + toStr(sum) + 'is.');
disposeAll(lines);
end.
First, we define a pointer called ItemPtr. Actually, in standard Pascal (ISO 7185 Pascal), variable length array cannot be used, so here we define a Node that can store only one numerical value called Item and input it in the form of a pointer. Changed to store the numerical value.
The main routine (the part below begin
) looks like this:
The message is displayed on the first line, and the numerical value (multiple lines) is accepted on the second line.
Then, in the 3rd line, the total of the entered numerical values is calculated, and in the 4th line, the result is displayed. toStr
is a function that converts an integer to a string, but this is also not in standard pascal, so it is implemented in the program.
The fifth line is freeing memory. You have to release the pointer yourself when you are done.
Now, let's consider the implementation of readLines
in the input part.
function readLines(): ItemPtr;
var
result: ItemPtr;
prevItemPtr: ItemPtr;
currItemPtr: ItemPtr;
line: ShortString;
begin
result := nil;
prevItemPtr := nil;
while true do
begin
write('>');
readln(line);
if line <> '' then
begin
if prevItemPtr = nil then
begin
new(prevItemPtr);
prevItemPtr^.text := line;
prevItemPtr^.next := nil;
result := prevItemPtr;
end
else
begin
new(currItemPtr);
currItemPtr^.text := line;
currItemPtr^.next := nil;
prevItemPtr^.next := currItemPtr;
prevItemPtr := currItemPtr;
end;
end
else
break;
end;
readLines := result;
end;
Enclose the whole in a while loop and make it a format that repeats until a condition (blank line feed input) is satisfied.
readln (line)
is the part that receives the character input.
Only the first time is special and keeps the entered value in prevItemPtr
. new (prevItemPtr)
is an instruction to allocate an area in heap memory, stores the input numerical value there, and saves it in the result ( result
).
From the second time onward, the entered value is retained in currItemPtr
. Then, the input numerical value is stored and referenced from the previous prevItemPtr
. Finally, update currItemPtr
as the new prevItemPtr
.
Next is the sumOfLines
function that calculates the addition.
function sumOfLines(lines: ItemPtr): Integer;
var
a : Integer;
v : Integer;
err: Boolean;
begin
a := 0;
while lines <> nil do
begin
err := false;
v := toInt(lines^.text, err);
if not err then
a := a + v;
lines := lines^.next;
end;
sumOfLines := a;
end;
This converts the string to an integer and adds it, checking the lines line by line. The toInt function that converts a character string to an integer is also defined in the program because it does not exist in standard Pascal. In addition, when a character string other than a numerical value arrives, it is judged by the second argument err so as not to cause an error. (Therefore, this second argument is a reference call)
The whole program looks like this:
program CalcSumApp;
type
ItemPtr = ^Item;
Item =
record
next: ItemPtr;
text: ShortString;
end;
function toInt(str: ShortString; var err: Boolean): Integer;
var
i : Integer;
n : Integer;
r : Integer;
begin
r := 0;
for i := 1 to ord(str[0]) do
begin
n := Ord(str[i]);
if (n < Ord('0')) or (n > Ord('9')) then
begin
err := true;
break;
end;
r := r * 10 + n - Ord('0');
end;
toInt := r;
end;
function toStr(n: Integer): ShortString;
var
m : Integer;
s : ShortString;
begin
s := '';
while n > 0 do
begin
m := n mod 10;
n := n div 10;
s := chr(m + ord('0')) + s;
end;
if s = '' then
s := '0';
toStr := s;
end;
procedure disposeAll(ptr: ItemPtr);
begin
if ptr <> nil then
begin
disposeAll(ptr^.next);
end;
end;
function readLines(): ItemPtr;
var
result: ItemPtr;
prevItemPtr: ItemPtr;
currItemPtr: ItemPtr;
line: ShortString;
begin
result := nil;
prevItemPtr := nil;
while true do
begin
write('>');
readln(line);
if line <> '' then
begin
if prevItemPtr = nil then
begin
new(prevItemPtr);
prevItemPtr^.text := line;
prevItemPtr^.next := nil;
result := prevItemPtr;
end
else
begin
new(currItemPtr);
currItemPtr^.text := line;
currItemPtr^.next := nil;
prevItemPtr^.next := currItemPtr;
prevItemPtr := currItemPtr;
end;
end
else
break;
end;
readLines := result;
end;
function sumOfLines(lines: ItemPtr): Integer;
var
a : Integer;
v : Integer;
err: Boolean;
begin
a := 0;
while lines <> nil do
begin
err := false;
v := toInt(lines^.text, err);
if not err then
a := a + v;
lines := lines^.next;
end;
sumOfLines := a;
end;
var
lines : ItemPtr;
sum : Integer;
begin
writeln('Please enter the number. It ends with a blank line.');
lines := readLines();
sum := sumOfLines(lines);
writeln('The total is' + toStr(sum) + 'is.');
disposeAll(lines);
end.
In Java, let's implement it with two classes, CalcSumApp
class and CalcSum
class. The first is the CalcSumApp
class for the entire application.
public class CalcSumApp {
public static void main(String args[]) {
CalcSum cs = new CalcSum();
CalcSumApp app = new CalcSumApp();
app.start(cs);
}
public void start(CalcSum cs) {
System.out.println("Please enter the number. It ends with a blank line.");
cs.readLines();
int sum = cs.getSum();
System.out.println("The total is" + String.valueOf(sum) + "is.");
}
}
In the main
function, create an instance of the CalcSumApp
class and the calcSum
class and call the start
method.
The contents of the start
method are almost the same as Pascal's main routine, but they do not deal directly with data, they just manipulate objects of the CalcSum
class.
Then the CalcSum
class looks like this:
class CalcSum {
List<String> list;
CalcSum () {
list = new ArrayList<String>();
}
public void readLines() {
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String line;
try {
do {
System.out.print('>');
line = input.readLine();
if ("".equals(line)) {
break;
}
list.add(line);
} while (true);
} catch (IOException e) {
}
}
public int getSum() {
int sum = 0;
for (String s : list) {
try {
sum += Integer.valueOf(s).intValue();
} catch (NumberFormatException e) {
}
}
return sum;
}
}
The readLines
method, which accepts input from the console, and the getSum
method, which calculates the sum, are defined. The contents are almost the same as the readLines
function and sumOfLines
function in pascal. However, the input numerical data (list) is retained as an instance variable of the CalcSum
class.
The whole program looks like this: Compared to Pascal, it is cleaner because there is no conversion process such as toInt or toStr, or process such as disposeAll of memory release, and it is nice that the list fits in CalcSum. (Although it may be better to make it private
)
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.NumberFormatException;
class CalcSum {
List<String> list;
CalcSum () {
list = new ArrayList<String>();
}
public void readLines() {
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String line;
try {
do {
System.out.print('>');
line = input.readLine();
if ("".equals(line)) {
break;
}
list.add(line);
} while (true);
} catch (IOException e) {
}
}
public int getSum() {
int sum = 0;
for (String s : list) {
try {
sum += Integer.valueOf(s).intValue();
} catch (NumberFormatException e) {
}
}
return sum;
}
}
public class CalcSumApp {
public static void main(String args[]) {
CalcSum cs = new CalcSum();
CalcSumApp app = new CalcSumApp();
app.start(cs);
}
public void start(CalcSum cs) {
System.out.println("Please enter the number. It ends with a blank line.");
cs.readLines();
int sum = cs.getSum();
System.out.println("The total is" + String.valueOf(sum) + "is.");
}
}
Since it is easy in this example, I think that it is possible to implement CalcSum as an immutable object, but in general, in object-oriented programming, I think that the objects that can be immutable are limited, so I will not touch on the implementation that makes it immutable. did.
In Haskell, the main routine looks pretty much the same, and looks like this:
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
putStrLn "Please enter the number. It ends with a blank line."
lines <- readLines getLines
list <- sequence lines
let s = sum list
putStrLn $ "The total is" ++ (show s) ++ "is."
The hSetBuffering stdout NoBuffering
part, as pointed out in the comments in this article, was that without it, buffering does not guarantee that the inputs and outputs will be in the expected order. (Thanks to @igrep)
getLines
is a list that receives an infinite number of strings and is defined as follows.
getLines :: [IO String]
getLines = do
repeat $ putStr ">" >> getLine
repeat
creates an infinite list. In other words
repeat x = [x x x x .... x ... ]
It's like that. It is a feature of Hakell's delay processing that it can handle such an infinite list, and I wanted to use it like this.
readLines
is a process to cut out the part until a blank line is input, and it is as follows.
readLines :: [IO String] -> IO [IO Int]
readLines [] = return []
readLines (l:ls) = do
v <- l :: IO String
if v == ""
then return []
else do
ll <- readLines ls
case readMaybe v of
Just x -> return $ (return x):ll
Nothing -> return ll
If the first element of the array is an empty string, that's it. Otherwise,
Convert the first element to a number (readMaybe
), and if it can be converted, add the converted one (recursively processed) and return it, and if it cannot be converted, convert the rest (recursively processed) Is returning only.
list <- sequence lines
It's a little confusing here, but I'm converting an array of IO to IO in an array and extracting its contents. So list
is just an array of numbers.
Originally here
list <- mapM (>>= return) lines
However, as pointed out in the comment, it can be solved in one shot with squence
, so I changed it.
And the calculation of the total is done next.
let s = sum list
(I will omit the story of strict evaluation because my understanding was wrong)
The whole program looks like this: The conversion of monads is a little troublesome, and unlike Java, the variable lines
is handled directly, but by using the do syntax, it can be described like pascal.
module CalcSumApp where
import Text.Read
import System.IO
getLines :: [IO String]
getLines = do
repeat $ putStr ">" >> getLine
readLines :: [IO String] -> IO [IO Int]
readLines [] = return []
readLines (l:ls) = do
v <- l :: IO String
if v == ""
then return []
else do
ll <- readLines ls
case readMaybe v of
Just x -> return $ (return x):ll
Nothing -> return ll
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
putStrLn "Please enter the number. It ends with a blank line."
lines <- readLines getLines
list <- sequence lines
let s = sum list
putStrLn $ "The total is" ++ (show s) ++ "is."
Even in Haskell, I tried to create a data type and encapsulate it in an object-oriented manner. I don't think it's practical, but ...
The main routine is
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
putStrLn "Please enter the number. It ends with a blank line."
calcSum <- newCalcSum -- calcSum = new CalcSum()
inputStrings calcSum -- calcSum.inputString()
sum <- getSum calcSum -- sum = calcSum.getSum()
putStrLn $ "The total is" ++ (show sum) ++ "is."
It looks like this, and I think it's closer to Java. In the whole code
module CalcSumApp2 where
import Text.Read
import Control.Monad
import Data.IORef
import Data.Maybe
import System.IO
data CalcSum = CalcSum { getStrings :: IORef [String] }
newCalcSum :: IO CalcSum
newCalcSum = do
ref <- newIORef []
return $ CalcSum ref
inputStrings :: CalcSum -> IO ()
inputStrings calcSum = do
let truncateStrings :: [IO String] -> IO [String]
truncateStrings [] = return []
truncateStrings (x:xs) = do
s <- x
if s == ""
then
return []
else do
l <- truncateStrings xs
return $ (:l) $! s -- strict evaluation of s corresponding to `return (s:l)`
list <- truncateStrings $ repeat $ putStr ">" >> getLine
writeIORef (getStrings calcSum) list -- calcSums.strings = list
getSum :: CalcSum -> IO Int
getSum calcSum = do
list <- readIORef (getStrings calcSum) :: IO [String] -- list = calcSum.strings
let nums = catMaybes $ readMaybe <$> list :: [Int]
return $ sum nums
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
putStrLn "Please enter the number. It ends with a blank line."
calcSum <- newCalcSum -- calcSum = new CalcSum()
inputStrings calcSum -- calcSum.inputString()
sum <- getSum calcSum -- sum = calcSum.getSum()
putStrLn $ "The total is" ++ (show sum) ++ "is."
What I wanted to say here is that the object (like thing) is ʻIORef. I think that objects have nothing to do with IO in the first place, but for me, objects are things that allocate an area in heap memory to read and write (an image like DB), so ʻIORef
I thought it would come nicely.
Objects in object-oriented languages are IORef-like objects (shared and modifiable), non-shared objects, immutable objects, immutable objects with no side effects (such as no IO input / output), and side effects. I think there are various things such as large objects (objects that wrap the processing that interacts with the external server), but I could not think about them well, so I will just present this sample here. I will keep it.
Standard Pascal has few features and you have to implement toInt
or toStr
yourself, or you have to free the memory yourself, but this (cumbersome) process is done. When I write it, I like to feel like "Oh, I'm programming."
I feel like I'm playing with a manual car instead of an automatic car. These days, Go is like this.
Java has been criticized in many ways, but I think it's an easy-to-use and good language. I feel that object-oriented programming languages are better suited for metaprogramming like Ruby and dynamically creating objects like JavaScript, but there is a sense of security that classes are statically determined. Sounds good. I like Java 1.5 or so, and it's a shame that it gets complicated with other paradigms.
Haskell has a lot to study and is struggling, but I'm very happy when it works. There are a lot of functions and it's hard to understand and remember, but it feels like it's a big deal when you know how to use it, and it feels like it's solved statically. I think it's good to be careful about the type.
In this simple example, it doesn't change much in any implementation, but I wondered if there are still some features of each language.
The number of programming languages these days is multi-paradigm, and that's why I think it's important to learn the basics of single-paradigm languages.
If you have any mistakes or inadequacies, please comment.
Recommended Posts