フューチャー技術ブログ

30種類のプログラミング言語で、ループ処理を書いてみた

はじめに

こんにちは。CSIG 所属の棚井です。
タイトルの内容が気になる方は、先に「こちら」をご覧ください。


本ブログは、Go 1.22 Release Notes の内容を紹介する「フューチャー技術ブログ Go 1.22 リリース連載」7本目の記事です。

Go 1.22 では、「澁川さんの記事」や「こちらの記事」にて取り上げられているように for ループのアップデート が入りました。アップデート分のうち、range over integerrange over function はこちらの issue(spec: add range over int, range over func #61405)に Proposal が記載されています。

本記事ではこのうち、range over integer にフォーカスして取り上げていきます。

Go 1.22 range over integer

Go の言語仕様が記載された For statements with range clause にて、for-range ループ は以下のように説明されています。

A “for” statement with a “range” clause iterates through all entries of an array, slice, string or map, values received on a channel, or integer values from zero to an upper limit [Go 1.22].

Range expression に渡す値のデータ型により、ループ変数にどのような値が渡されるのかが整理されています。例えば、Range に渡したデータ型が配列やスライスであれば、1st value と 2nd value に渡される値は「int型のインデックス、インデックスに対応する値」であり、マップ型であれば「キー、キーに対応する値」という言語仕様が説明されています。

Range expression                          1st value          2nd value

array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E
integer n integer type value i see below

Range の説明に see below と記載された項目のうち、integer については以下の説明が Go 1.22 から追記されています。

  1. For an integer value n, the iteration values 0 through n-1 are produced in increasing order. If n <= 0, the loop does not run any iterations.

range over integer の挙動としては

  • range に渡された n が integer型の場合は
    • n > 0 ならば、0 から n-1 まで +1 だけインクリメントしながらループを継続する
    • n <= 0 ならば、ループしない

との説明があります。

実際に Go 1.22(release-branch.go1.22)を動かしてみると、仕様通りの挙動が得られました。

package main

import (
"fmt"
)

func main() {
for i := range 5 {
fmt.Println(i)
}
}

// Output:
// 0
// 1
// 2
// 3
// 4
package main

import (
"fmt"
)

func main() {
for i := range 0 {
fmt.Println(i)
}

for i := range -1 {
fmt.Println(i)
}
}

// Output:(なし)

Range expression に自然数を渡した場合は、0からその数分だけ+1インクリメントしながらループが繰り返されていること、また、0以下の整数を渡した場合にはループ自体がスキップされていることが分かります。

Go 1.21 以前に「指定の回数だけループを繰り返したい」場合には以下のような「C言語から続く伝統的なループ処理」で実装する必要がありました。

package main

import (
"fmt"
)

func main() {
// C言語からの伝統のループ
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}

「伝統的なループ処理」のコードレビューでは「あー、for で回数指定のループね。ここの処理は配列やマップに依存していないから、拡張for文は使えないのか。ループの開始値は x で、終了条件は x < y だから、ループ回数は z になる。よし、テストコード側も確認しよう」のように「境界条件を脳内でイメージした上、ループ回数を演算して、妥当性を確認する」必要がありました。今後は「伝統的なループの記述が不要」になりましたので、Go 1.22 からは少しだけレビューが楽になりました。予めループの回数が決まっている処理は range over integer で書き換えられると思いますので、発見次第リファクタリングの Pull Request を作成してみてはいかがでしょうか。

色々なプログラミング言語の「ループ処理」を見てみる

Go 1.22 連載での「渋川さんの記事」にある C言語からの伝統のループ という文を見て

  • そういえば、他のプログラミング言語では、ループ処理をどうやって書いてたっけ?
  • 開発言語を変えた直後は、絶妙な違いでコンパイルエラーや文法エラーにぶつかっている気がする

との感情が湧き出てきました。

さらに、ちょうど今読み進めている「達人プログラマー」の「達人の哲学」には

毎年少なくとも一つの言語を習得する。
言語が異なると、同じ問題でも違った解決方法が採用されています。つまり、いくつかの異なったアプローチを学習することにより、幅広い思考が可能になるわけです。

と書かれていることにインスパイアされましたので、
30種類のプログラミング言語で、同じ出力が得られるループ処理
を書いてみたら何か学びがあるのかなと思い、調べてみました。

これまでの業務で利用した言語もあれば、名前を聞いたことはある程度の言語、せっかくの機会なので「(私にとっては)伝説の言語」までピックアップしています。

肝心のループ処理内容は、Go 1.22 Release Notesexample として提示されている以下コードをベースとしています。

package main

import "fmt"

func main() {
for i := range 10 {
fmt.Println(10 - i)
}
fmt.Println("go1.22 has lift-off!")
}

// Output:
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1
// go1.22 has lift-off!

また、言語選定には、以下サイトを参考にしています。

それでは、30種類のプログラミング言語でのループ処理を見ていきましょう。

対象言語

  1. Python
  2. Ruby
  3. Java
  4. JavaScript
  5. TypeScript
  6. C言語
  7. C++
  8. Bash
  9. PHP
  10. Rust
  11. Kotlin
  12. Swift
  13. Objective-C
  14. C#
  15. Dart
  16. Perl
  17. Scala
  18. Haskell
  19. Prolog
  20. R言語
  21. Julia
  22. FORTRAN
  23. Lisp
  24. MATLAB
  25. PowerShell
  26. Vim Script
  27. Emacs Lisp
  28. Visual Basic .NET
  29. COBOL
  30. RPG
  31. Smalltalk

実行時の Output

10
9
8
7
6
5
4
3
2
1
go1.22 has lift-off!

本ブログでは「実装の正しさ」よりも「各言語ごとのループ処理の雰囲気を味わうこと」を優先してます。
「その実装だと、ループ回数が多いよ!」などの粗が見つかるかもしれませんが、温かい目で見守っていただけると幸いです。

Python

for i in range(10):
print(10 - i)

print("go1.22 has lift-off!")
for i in range(0, 10):
print(10 - i)

print("go1.22 has lift-off!")

Ruby

10.times do |i|
puts 10 - i
end

puts "go1.22 has lift-off!"
for i in 0..9 do
puts 10 - i
end

puts "go1.22 has lift-off!"

Java

public class HelloWorld {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(10 - i);
}

System.out.println("go1.22 has lift-off!");
}
}
void main() {
for (int i = 0; i < 10; i++) {
System.out.println(10 - i);
}

System.out.println("go1.22 has lift-off!");
}

JavaScript

for (let i = 0; i < 10; i++) {
console.log(10 - i);
}

console.log("go1.22 has lift-off!");

TypeScript

for (let i: number = 0; i < 10; i++) {
console.log(10 - i);
}

console.log("go1.22 has lift-off!");

C言語

#include <stdio.h>

int main() {
for (int i = 0; i < 10; i++) {
printf("%d\n", 10 - i);
}

printf("go1.22 has lift-off!\n");
return 0;
}

C++

#include <iostream>

int main() {
for (int i = 0; i < 10; i++) {
std::cout << 10 - i << std::endl;
}

std::cout << "go1.22 has lift-off!" << std::endl;
return 0;
}

Bash

#!/bin/bash

for i in {0..9}
do
echo $((10 - i))
done

echo "go1.22 has lift-off!"

PHP

<?php

for ($i = 0; $i < 10; $i++) {
echo 10 - $i . "\n";
}

echo "go1.22 has lift-off!\n";
?>

Rust

fn main() {
for i in 0..10 {
println!("{}", 10 - i);
}

println!("go1.22 has lift-off!");
}

Kotlin

fun main() {
for (i in 0 until 10) {
println(10 - i)
}

println("go1.22 has lift-off!")
}

Swift

import Foundation

for i in 0..<10 {
print(10 - i)
}

print("go1.22 has lift-off!")

Objective-C

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 10; i++) {
NSLog(@"%d", 10 - i);
}

NSLog(@"go1.22 has lift-off!");
}
return 0;
}

C

using System;

class Program
{
static void Main()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(10 - i);
}

Console.WriteLine("go1.22 has lift-off!");
}
}
for (int i = 0; i < 10; i++)
{
Console.WriteLine(10 - i);
}

Console.WriteLine("go1.22 has lift-off!");

Dart

void main() {
for (int i = 0; i < 10; i++) {
print(10 - i);
}

print("go1.22 has lift-off!");
}

Perl

#!/usr/bin/perl

use strict;
use warnings;

for my $i (0..9) {
print 10 - $i, "\n";
}

print "go1.22 has lift-off!\n";

Scala

object Countdown {
def main(args: Array[String]): Unit = {
for (i <- 0 until 10) {
println(10 - i)
}

println("go1.22 has lift-off!")
}
}

Haskell

main :: IO ()
main = do
let countdown = [0..9]
mapM_ (\i -> putStrLn (show (10 - i))) countdown

putStrLn "go1.22 has lift-off!"

Prolog

start_countdown :- 
between(0, 9, N),
writeln(10 - N),
N = 9,

writeln("go1.22 has lift-off!").

R言語

for (i in 0:9) {
cat(10 - i, "\n")
}

cat("go1.22 has lift-off!\n")

Julia

for i in 0:9
println(10 - i)
end

println("go1.22 has lift-off!")

FORTRAN

program Countdown
implicit none
integer :: i

do i = 0, 9, 1
print *, 10 - i
end do

print *, "go1.22 has lift-off!"
end program Countdown

Lisp

(dotimes (i 10)
(format t "~a~%" (- 10 i)))
(format t "go1.22 has lift-off!~%")

MATLAB

for i = 0:9
disp(10 - i)
end

disp('go1.22 has lift-off!')

PowerShell

for ($i = 0; $i -lt 10; $i++) {
Write-Host (10 - $i)
}

Write-Host "go1.22 has lift-off!"

Vim Script

for i in range(0, 9, 1)
echo 10 - i
endfor

echo "go1.22 has lift-off!"

Emacs Lisp

(dotimes (i 10)
(message "%d" (- 10 i)))

(message "go1.22 has lift-off!")

Visual Basic .NET

Imports System

Module Program
Sub Main()
For i As Integer = 0 To 9
Console.WriteLine(10 - i)
Next i

Console.WriteLine("go1.22 has lift-off!")
End Sub
End Module

COBOL

IDENTIFICATION DIVISION.
PROGRAM-ID. Countdown.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 I PIC 9(2) VALUE 0.

PROCEDURE DIVISION.
PERFORM VARYING I FROM 0 BY 1 UNTIL I > 9
DISPLAY FUNCTION NUMVAL(10 - I)
END-PERFORM.

DISPLAY "go1.22 has lift-off!".
STOP RUN.

RPG

**FREE
/COPY QRPGLESRC,QRPGLECPY

DCL-S i INT(2);

FOR i = 0 TO 9;
DSPLY (' ' + %CHAR(10 - i));
ENDFOR;

DSPLY 'go1.22 has lift-off!';
*INLR = *ON;

/END-FREE

Smalltalk

1 to: 10 do: [:i |
(11 - i) displayNl.
].
'go1.22 has lift-off!' displayNl.

おわりに

30種類の言語を書く予定でしたが、ブログ前半の Go と最後の Smalltalk を合わせて、合計 32 個のループ処理となりました。

普段は Go で開発しており、久しぶりに見た Java の文法で パブリックスタティックヴォイドメイン を回避する方法が出てきた、というのが一番の発見でした。

JEP 445: Unnamed Classes and Instance Main Methods (Preview) で「Hello World!」のコードが

There is too much clutter here — too much code, too many concepts, too many constructs — for what the program does.

Educators often offer the admonition, “don’t worry about that, you’ll understand it later.” This is unsatisfying to them and their students alike, and leaves students with the enduring impression that Java is complicated.

と言われているのは、「それはそう」と久しぶりに仕様書で笑いました。

ここまで読んでいただけた方であれば、本記事冒頭の「Go 1.22 で追加された range over integer の仕様紹介」はメイントピックではなく、「色々なプログラミング言語を書いてみること」が執筆モチベーションになっていることをご理解いただけると思います。Go 1.22 のマイナーアップデートという「きっかけ」を利用して、多種多様なプログラミング言語を眺めてみました。

また、執筆後に クジラ飛行机 さんの書かれた「プログラミング言語大全」を見つけまして、早速購入させていただきました。

私自身の開発言語経験と比較しますと、今回取り上げたプログラミング言語の殆どは初見です。
達人プログラマーの「毎年少なくとも1つの言語を習得する」には「習得する」とあります。そのレベルまで達成している言語を増やせるように、これからも頑張らねばと思いました。