はじめに こんにちは。CSIG 所属の棚井です。 タイトルの内容が気になる方は、先に「こちら 」をご覧ください。
本ブログは、Go 1.22 Release Notes の内容を紹介する「フューチャー技術ブログ Go 1.22 リリース連載 」7本目の記事です。
Go 1.22 では、「澁川さんの記事 」や「こちらの記事 」にて取り上げられているように for ループのアップデート が入りました。アップデート分のうち、range over integer
と range 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 から追記されています。
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 mainimport ( "fmt" ) func main () { for i := range 5 { fmt.Println(i) } }
package mainimport ( "fmt" ) func main () { for i := range 0 { fmt.Println(i) } for i := range -1 { fmt.Println(i) } }
Range expression
に自然数を渡した場合は、0からその数分だけ+1インクリメントしながらループが繰り返されていること、また、0以下の整数を渡した場合にはループ自体がスキップされていることが分かります。
Go 1.21 以前に「指定の回数だけループを繰り返したい」場合には以下のような「C言語から続く伝統的なループ処理」で実装する必要がありました。
package mainimport ( "fmt" ) func main () { 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 Notes に example として提示されている以下コードをベースとしています。
package mainimport "fmt" func main () { for i := range 10 { fmt.Println(10 - i) } fmt.Println("go1.22 has lift-off!" ) }
また、言語選定には、以下サイトを参考にしています。
それでは、30種類のプログラミング言語でのループ処理を見ていきましょう。
対象言語
Python
Ruby
Java
JavaScript
TypeScript
C言語
C++
Bash
PHP
Rust
Kotlin
Swift
Objective-C
C#
Dart
Perl
Scala
Haskell
Prolog
R言語
Julia
FORTRAN
Lisp
MATLAB
PowerShell
Vim Script
Emacs Lisp
Visual Basic .NET
COBOL
RPG
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!" ); } }
2023/09/19 にリリースされた Java 21 のプレビュー機能では、以下の表記が可能
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 Foundationfor 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
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 SystemModule 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つの言語を習得する」には「習得する」とあります。そのレベルまで達成している言語を増やせるように、これからも頑張らねばと思いました。