概要
プログラム言語の文法はいろいろなので Hello World だけではその言語の特徴は把握できないですよね。
そこで、基本的な文法の理解&比較&備忘録として、ペントミノのソルバーを20種類のプログラミング言語で書いてみました。
どの言語も200~300行程度のコードなので全体を把握するのは難しくないと思います。
Ruby Python Lua Squirrel Julia JavaScript TypeScript Groovy Perl AWK PHP C C++ C# Java Kotlin Go Rust Swift LISP Crystal Elixir F# |
(2024/12/9: Crystal、Elixir を追加、実行速度グラフの修正)
比較のポイント
- 関数の宣言方法:引数、返り値
- 変数の宣言方法:型指定、ローカル、グローバル、mutable/immutable
- 配列:作り方、操作メソッド、多次元
- 文字列:操作メソッド、変数埋込、複数行文字列
- 連想配列
- NULL値: null, nil, nothing, ....
- print文: 書式指定方法
- コメント文
- ブロックの書式: {} 、~ end
- for, foreach, if の構文
- 三項演算子
- lambda、closure、アロー関数
- クラス:メンバ変数、メソッド、コンストラクタ
- コマンドライン引数
- 実行時間
プログラム
ソースコード
https://github.com/sense-n-react/PentominoSolvers
プログラムの特徴としては、ひたすら配列アクセスや関数呼び出しを行うものです。これだけで言語の良し悪しを評価することはできませんが、素(す)の言語の使い勝手を感じることはできると思います。
プログラム全体の処理の流れ、構成(見た目)、クラス名、関数名、変数名はできるだけ同じにしています。オブジェクト指向的な作りを心掛けましたが、AWK、Perl、LISP はベタな書き方で妥協しています。
実行方法
ソルバープログラムは個別に実行できます。表示はキャラクターベースです。
$ ruby solver.rb
+---+---------------+---+
| | | |
| +-------+ +---+ |
| | | |
+-------+ +---+ +---+
| | | | |
| +---+-------+ |
| | | |
+---+ +-------+ | |
| | | | | |
| +---+---+ | | |
| | | | |
| +---+---+ +---+ |
| | | | | |
+---+ +---+---+ +---+
| | | |
+---+ +---+ +---+ |
| | | | |
| +---+-------+ | |
| | | |
+---------------+---+---+
2339
盤面のサイズ指定
デフォルトでは 6x10
の盤面で解を求めます。コマンドラインに 8x8
のように盤面のサイズを指定できます。指定可能なサイズは 3x20
4x15
5x12
6x10
10x6
12x5
14x5
20x3
8x8
4x16
16x4
のいずれかです。これ以外だと 6x10
として実行します。
8x8
や 4x16
では中央に 4x4
のスペースを配置して解を求めます。
$ ruby solver.rb 8x8
+---+-----------+---------------+
| | | |
| +-------+ | +-----------+
| | | | |
+-------+ | +---+ +-------+
| | | | | |
+---+ +---+---+---+---+ +---+
| | | | | |
| +---+ | +---+ | |
| | | | | | |
| +---+---+---+---+ +---+ |
| | | | | |
| | | +---+ +---+ |
| | | | | | | |
+---+ +---+ | +---+ | |
| | | | |
+-----------+---+-----------+---+
65
run-solver
言語が20種類もあると言語のコマンドをいちいち覚えてられない(特にコンパイル系)ので、run-solver というRubyスクリプトを作りました。
ファイルの拡張子を指定すれば対応する言語のプログラムを実行します。
コンパイル系言語ではコンパイルしてから実行します。
$ ./run-solver rb # ruby solver.rb を実行
$ ./run-solver rb js cpp # 複数指定可
$ ./run-solver ruby # 言語名でもOK
$ ./run-solver --all # 全ての言語で実行
$ ./run-solver py --size 5x12 # 5x12 を python で実行
感想
以下は、各言語に対する私の感想です。理解不足や偏見がありますのでご注意ください。
-
Ruby
シンプルで一番好きです。慣れてることもあり「考えていることをそのまま書く」ことができます。特に、メソッドチェーンを使ってシンプルに書けるところが良いです。 -
Python
Rubyと同程度にシンプルに書けます。が、メソッドチェーンで不自由を感じました。実行速度は意外に遅いです。内包表記や3項演算子などが他の言語と比べて独特で取っ付きにくいです。インデントも賛否両論・・・。ユーザの多さや豊富なライブラリはメリットと言えますが、言語として好みではありません。 -
JavaScript
実行速度が非常に速いので驚きました。JITの効果を体感できます。言語仕様もいい感じで癖がないと思います。let と var の使い分けは少し億劫です。solver.js では使っていませんが、JavaScript の非同期処理の書き方は直感的でなくて好きではありません。 -
TypeScript
型チェック、、、頑張ってるなぁと思います。もちろんパフォーマンスは JavaScript と同じです。tsc 自体が JavaScript で書かれていることにも驚きです。
オプション指定はtsconfig.json
を使うのが作法のようですが、ファイル1個を動かすだけだしコマンドラインオプションで対応しました。結構試行錯誤しました。 -
Julia
着実に進歩してますね。数値計算に向いてるらしいのでオブジェクト指向でガンガン...というのではないようです。配列が [1] から始まるのが... -
Lua
コンパクトなのにパフォーマンスが良くて、面白い言語です。 メンバ変数は '.' なのに メソッドは ':' とか、配列が [1] から始まるのが残念です。玄人向けだと思います。 -
Squirrel
Lua と同じくコンパクトで、C++ を知ってれば使えるという感じです。拡張子が nut なのがおしゃれです。
グローバル変数の扱いに戸惑いました。ローカル変数につけないといけない local が目につきます。エラーメッセージが判りにくいのと REPLが使いにくいのが残念です。 -
Groovy
ググったら JAVA VM で動く動的言語が見つかったので味見してみました。型があるけど Ruby に似てる感じがあって抵抗なく使えました。
ファイル名と同じ変数名が使えなくて Java のニオイがしました。solver_ = new Solver( 6, 10 )
と _ を付けているのはエラー回避のためです。 -
Perl
ローカル変数がmy
って・・・・イケてないと思います。配列変数が@
や$
だったり、参照渡しの場合は\
や[]
が必要だったり、、、戸惑うし理解が難しいです。$Pieces{"F"} = [@{$Pieces{"F"}}[0..1]]
なんて変態です。
間違っていても中途半端に動いてしまうことがあり、初学者には難しいだけでなく危険だと思います。 -
AWK
ペントミノは AWK にとって可哀そうだけど頑張りましたって感じです。 AWK の連想配列はすばらしいです。配列が [1]から始まるのは AWK だから許します。
mawk, nawk, gawk などの派生がまだ使われていて AWK の歴史を感じるけれど、プログラミング言語としては過去の言語だと思います。 -
PHP
WEB開発用の言語でしょ?ということぐらいしか知らず、ペントミノをPHPでやるもんじゃないと思いながらもやってみたらできました。言語としてはシンプルで理解しやすいし、パフォーマンスも悪くないです。
やたらと$
と->
が目に付くのでそこは好きになれないです。行末の;
を忘れて怒られることが多いです。 -
C
C言語を使うことはホントに少なくなりました。メモリを意識しないといけないし、文字列の扱いが不自由だし、、、不便な言語です。実行速度はもちろん速いです。使い勝手としてはセンブリ言語みたいな感じがします。 -
C++
STL を使えば使い勝手はスクリプト言語に(少し)近づきます。STLの多用は実行速度に影響するので、書きやすさとパフォーマンスはトレードオフです。 -
C#
あまり親しみがないのでこんなものなのかな・・・という感じです。Java や C++ を知っていれば使えるのでしょう。Linuxでも動くのでもう少し使ってみようかな・・・ -
Java
面倒くさい印象があり、昔から使い易い(書き易い)とは思っていません。食わず嫌いです。ファイル名を小文字の solver.java にできなかったのが悔しいです。
ubuntu の java はバージョンが古くて Text Block に対応してないので、それに合わせてPIECE_DEF_DOC
はベタな書き方にしています(残念)。 -
Kotlin
Java よりも格段に良いです。Java の面倒くささを隠蔽してくれて、メソッドチェーンも良い感じに書けて使いやすいです。これで Java を使わずに VM のアプリが作れます。実際に私は Android アプリを Kotlin で作りました。 -
Rust
私には無茶苦茶とっつき難いです。コンパイルエラーを消すのに試行錯誤・・・どうしても unsafe を取ることもできず・・・。腰を落ち着けて勉強しないとだめですね。ポインタをガチャガチャいじるようなアプリには向いてないのかもしれません。
エラーメッセジーはとても親切です。 -
Go
文法に独特感があって、触ってないと忘れてしまいます。このソルバープログラムを備忘録にします。 -
Swift
Objective-C を試そうと思ったけれど Mac以外で環境を作るのが面倒そうだったので諦めて、代わりに Swift を選びました。
引数の型宣言が面倒でしたがあまり癖は感じなかったです。Mac以外で使うことあるのかなぁ・・・ -
LISP
今まで避けていましたが、興味があったので使ってみました。なんとなくLISPの心が解った気がします。でも、独特過ぎて書き易いとも見易いとも思いません。emacs の 設定が読めるようになった(たぶん)のでトライして良かったです。 -
Crystal
存在を知りませんでした。 ほぼ Rubyのように書けます。頑張ってると思います。C や C++ と同じとは行きませんが実行速度は速いです。頑張ってると思います。 -
Elixir
関数型言語の特徴を体験できます。表記方法に Ruby のニオイがします。すべてのデータが imutable なので、List、Map の要素を書き換えるという基本的な操作に手間取ったり、グローバル変数という概念がないので面食らいます。従来のプログラムではいかに副作用に依存していたかということを痛感します。パイプ演算子(|>)は面白いです。 -
F#
Elixir 以外の関数型言語もと思い試してみました。実行速度は速いです。表記方法は Python のニオイがするし、他にもクセの強さを感じました。また、match ~ with が難しい、エラーメッセージが解りにくい、など難しさも感じました。
動作環境
Linux ubuntu:22.04、Mac Sonoma:14.4、Cygwin(Windows)で動作確認しました。
tsc (TypeScript Compiler) では、npm i --save-dev @types/node
を一度だけ実行しておく必要があります。 実行していないとエラーがでます。
$ tsc -t es2019 -m commonjs --strict solver-ts.ts
solver-ts.ts(202,18): error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
$ npm i --save-dev @types/node # 指示通りに npm i ...を実行
$ ls node_modules # node_modules ディレクトリが作られる
@types/ undici-types/
$ tsc -t es2019 -m commonjs --strict solver-ts.ts # エラーにならずに実行できる
ubuntu 22.04
Mac と Windows(WSL2) でDocker コンテナに全ての言語をインストールして動作確認しました。Dockerfile で x86_64 でなければ arm64 の Julia と Swift をダウンロードするように設定しています。
Ruby 3.0.2 |
Python 3.10.12 |
Lua 5.1.5 |
Squirrel 3.2 |
Julia 1.10.2 |
JavaScript node 12.22.9 |
TypeScript tsc 4.9.5 |
Groovy 2.4.21 |
Perl 5.34.0 |
AWK mawk 1.3.4 |
PHP 8.1.2 |
C gcc 11.4.0 clang 15.0 |
C++ g++ 11.4.0 clang++ 15.0 |
C# mcs 6.8.0.105 |
Java javac 11.0.22 |
Kotlin kotlinc-jvm 1.3 |
Go 1.18.1 |
Rust rustc 1.75.0 |
Swift 5.10 |
LISP sbcl 2.1.11 clisp 2.49.93 |
Crystal 1.14.0 LLVM: 18.1.6 |
Elixir 1.12.2 |
F# 6.0 fsi 12.0.0.0 dotnet 6.0.135 |
apt-get でインストールしましたが、Julia, Swift, Squirrel はパッケージが見つかりませんでした。Julia と Swift はバイナリイメージがあったのでそれをダウンロードし、Squirrel はソースからビルドしています。
Crystal は Dcokerfile に反映していないですが、x86_64版 tarball で動作確認しました。
Mac Sonoma
brew install でインストールしました。
(追記)F# は Microsoftの.NETのダウンロードサイトからダウンロードしました。
(2024/12 時点)
Ruby 3.2.0 |
Python 3.13.0 |
Lua 5.4.7 |
Squirrel 3.2 |
Julia 1.11.1 |
JavaScript node 23.1 |
TypeScript tsc 4.9.5 |
Groovy 4.0.24 |
Perl 5.34.1 |
AWK gawk 5.3.1 nawk 20200816 |
PHP 8.3.13 |
C clang 15.0.0 |
C++ clang++ 15.0.0 |
C# mcs 6.12.0.182 csc 3.9.0 |
Java javac 23.0.1 |
Kotlin kotlinc-jvm 2.0.21 |
Go 1.23.2 |
Rust rustc 1.76.0 |
Swiftc 1.90.1 |
LISP sbcl 2.4.10 clisp 2.49.92 |
Crystal 1.14.0 |
Elixir 1.17.3 |
* F# 9.0 fsi 12.9.100.0 dotnet 9.0.101 |
Cygwin(Windows)
Cygwin Ports にないものは Windows版をインストールし(*印)、 Windows版もないものはソースからビルド(**印)しました。
Ruby 3.2.2 |
Python 3.9.16 |
Lua 5.3.6 |
** Squirrel 3.2 |
* Julia 1.8.2 |
* JavaScript node 20.12.2 |
* TypeScript tsc 4.9.5 |
* Groovy 2.5.23 |
perl 5.36 |
AWK gawk 5.3.0 |
PHP 7.3.7 |
C gcc 11.4.0 clang 8.0.1 |
C++ g++ 11.4.0 clang++ 8.0.1 |
C# csc 4.8.4084.0 |
*Java javac 21.0.2 |
* Kotlin kotlinc-jvm 1.7.20 |
* Go 1.19.4 |
* Rust rustc 1.70.0 |
* Swift 5.10 |
LISP *sbcl 2.3.2 clisp 2.49 |
* Crystal 1.13.3 LLVM: 18.1.1 |
* Elixir 1.17.3 |
* F# 9.0 fsi 12.9.100.0 |
実行時間
やはり気になる実行時間をグラフにしてみました。MacBook M2 Sonoma の環境で、6x10 の全解(2,339通り)を求めるのにかかった時間をプロットしました。
スクリプト系とコンパイル系とでは桁が違ってくるので対数表示にしています。
なお、アルゴリズムを改良すれば全ての言語において10倍以上の高速化が可能ですが、ここでは言語間の比較が目的なので最適化には触れません。
参考記事