なかなかすごいことになった私のスマホの開発環境
最近の私はほとんどスマホでプログラミングする。通勤時間帯がプログラミング時間というスキマプログラミングである。
Android版のLinuxである「Termux」を利用しているんだが、これが実によくできている。入力したコマンドがなければ『○○をインストールして!』と教えてくれる。インストールすればほぼ悩みなく動作する。
ちょいとプログラミングしてみようと思ったが、自宅でPCを立ち上げてあーだこーだする時間がなかった。スマホでプログラミングは苦肉の策だったんだが、予想以上の環境である。
Linuxは、おそらく最もオープンソースが進んでいるプラットホームではないか。
さて!
今、私のスマホに入っているコンパイラ達である。
C言語
#include <stdio.h>
int main()
{
printf("Hello World.\n");
return 0;
}
まずは、C言語。
開発は1972年と古い。
「Hello World」だけでも既にいろいろお作法が見えている。
まず、「stdio.h」をインクルードする。「printf」を使うためには必ずインクルードしなければならない。「printf」に限らずライブラリを使用しようと思うといろいろインクルードしなければならない。
そして「main」関数。必ず1つの「main」関数が必要である。また2つ以上あってもいけない。どのプログラムもこの「main」関数からスタートする。
「main」関数の型は「int」。これも必ず「int」でなければならない。戻り値は正常終了なら0、異常終了なら0以外を返す。これはシェルスクリプトからこのプログラムを呼び出すときに意味を持つようになる。
さらに関数は {} で括らなければならない。これも絶対に必要。
「printf」は標準出力に表示する標準関数である。コードの1文ごとに、これも必ず「;」(セミコロン)が必要である。逆に「;」さえ付ければ一行に複数の文を書くこともできる。
最後は「return」文。関数の型が「void」でなければこれも必ず必要。今回の場合は0(正常修理)固定。実は「printf」にも戻り値があって異常終了という可能性もあるんだが、異常終了する条件もなんとなくはっきりせず、標準出力に表示できないからといって何をどうするねんというのもあって、「printf」文の戻り値はチェックしないことも少なくない。
コンパイル、実行は次の通り。
$ l
hello_c.c
$ cat hello_c.c
#include <stdio.h> int main()
{
printf("Hello World.\n"); return 0;
}
$ cc hello_c.c -o hello_c
$ l
hello_c* hello_c.c
$ ./hello_c
Hello World.
$
C++
#include <iostream>
int main()
{
std::cout << "Hello World." << std::endl;
}
C++である。1983年に登場。
C言語の後継言語で、オブジェクト指向の諸々を機能追加した。後継ではあるのだが、原則として従来のC言語をそのまま使用できることを前提とした。C言語の資源が多く、それが使えないとなるとC++に移行するものが続かないだろうと考えてのことかと思われる。逆に負の遺産も引き継いだとも言える。ヘッダファイルはなくしてほしかった。
標準出力は「cout」を使用する。「printf」も使えるんだが。「printf」は「10進数で表示しろ」とか「文字列として表示しろ」とか、いちいち指示してやらなければならない。「cout」は、表示しようとしているデータの型を把握して、それなりにふさわしいと思われるパターンで適当に表示してくれる。
また、()で引数を指定するのではなく、演算子「<<」をオーバーライトしている。これもC++の成せる技である。
コンパイル、実行は次の通り。
$ l
hello_cpp.cpp
$ cat hello_cpp.cpp
#include <iostream>
int main()
{
std::cout << "Hello World." << std::endl;
return 0;
}
$ clang++ hello_cpp.cpp -o hello_cpp
$ l
hello_cpp* hello_cpp.cpp
$ ./hello_cpp
Hello World.
$
Python
print("Hello World.")
最近流行りのPythonである。1991年に登場したインタプリタ言語である。
うーん。
たった1行になってしまった。
「print」関数はPythonの標準関数で、標準関数は何を宣言することもなく使用できる。インタプリタなので関数定義も必ずしも必要ではない(関数も定義したければできる)。1行なので、もはや解説することもない。
実行してみた。
$ l
hello_py.py
$ cat hello_py.py
print("Hello World.")
$ python hello_py.py
Hello World.
$
Java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World.");
}
}
1995年に登場したプログラミング言語である。
えーっと。Javaは詳しくないし、面倒くさくもあるのでAIに教えてもらったことをそのまま張り付ける。
引用文中にある『[こちら]のサイト』については省略。
少し補足する。
Javaは、C++をさらに発展させた言語である。特徴はいろいろあるだろうが、一番大きいのはプラットホームに依存しないということだろうか。Javaが出てきた当初に抱いた実感である。C++は、プラットホームに依存する。Windows対象で作ったオブジェクトはWindowsでしか動かないし、Mac対象で作ったオブジェクトはMacでしか動かないし、Android対象で作ったオブジェクトはAndroidでしか動かない。それだけではない。Intel対象のオブジェクトはIntelでしか動かないし、ARM対象のオブジェクトはARMでしか動かない。CPUとOSの両方に依存する。それがプラットホーム依存だ。Javaはその敷居を取り除いた。Java仮想マシンの導入である。それぞれのCPU、そしてOSに対応したJava仮想マシンを用意する。Javaのソースコードは、そのJava仮想マシンの上で動作する。これでプラットホームと直に接することはなくなる。プラットホーム依存からの脱却である。ネットワークという時代の流れがJavaの背中を押した。
その他、Javaという言語仕様にはいろいろ思いが込められているようだが、それらについて言及するにはこの余白はあまりにも小さすぎる。なのでまたそのうちに(多分)。
実行してみる。
$ l
hello_java.java
$ cat hello_java.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World.");
}
}
$ java hello_java.java
Hello World.
$
C#
// C#でHello Worldを出力するプログラム
// Systemネームスペースを使用する
using System;
// Programクラスを定義する
class Program
{
// Mainメソッドを定義する
static void Main(string[] args)
{
// ConsoleクラスのWriteLineメソッドを呼び出して、
// "Hello World!"という文字列を出力する
Console.WriteLine("Hello World!");
}
}
C#も使える。驚きである。
C#は2000年にMicrosoftが開発した言語である。
生成した実行ファイルはTermux上で直接的には動かない。「mono」から動かす。「mono」はクロスプラットホームの「.NET Framework」だそうだ。C#にとって「.NET Framework」は欠かせない。
さて、コードはというと、Javaにそっくりである。
いちいち列挙するのも面倒なくらいそっくりである。
何の主張も見いだせないくらいそっくりである。
ちなみに、先に出たのはJavaで、C#は後発である(別に何が言いたいというわけでもない)。
クラス名「Program」とは、また実にグローバルなネーミングである。このネーミングを考えたのはAIだが、そのまま許容されるようである。Javaなら悶絶しそうだ(ファイル名とクラスが一致いないとコンパイルエラーになった)。
そしてやはり「Main」がいるのか。右にならえ(過去にならえ)なのか、「Main」を設けるほうが楽なのか(プログラマーが? それともコンパイラが?)。
コンパイル&実行
$ ls
hello_c#.cs
$ cat hello_c#.cs
// C#でHello Worldを出力するプログラム
// Systemネームスペースを使用する
using System;
class Program // Programクラスを定義する
{
// Mainメソッドを定義する
static void Main(string[] args)
{
// ConsoleクラスのWriteLineメソッドを呼び出し て、
// "Hello World!"という文字列を出力する
Console.WriteLine("Hello World!");
}
}
$ mcs hello_c#.cs
$ ls
hello_c#.cs hello_c#.exe
$ ./hello_c#.exe
bash: ./hello_c#.exe: cannot execute binary file: Exec format error
$ mono ./hello_c#.exe
Hello World!
$
Swift
print("Hello World!")
だんだんと未経験言語の領域に入ってゆく。
今度はApple言語である。2014年開発。
また1行コードだ。
言うことなし。
実行結果
$ ls
hello_swift.swift
$ cat hello_swift.swift
print("Hello World!")
$ swift hello_swift.swift
Hello World!
$
Golang
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
次。
Google言語。2009年開発。
似たり寄ったりを見てきてだんだん飽きてきた(笑)。
「package」の名称が「main」で、関数も「main」だが、「main」でなくてもいいのか、それとも「main」が必須なのか。
「fmt」は何の略称なるぞ?
「format」?
実行、コンパイル
$ ls
hello_go.go
$ cat hello_go.go
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
$ go hello_go.go
go hello_go.go: unknown command
Run 'go help' for usage.
$ go run hello_go.go
Hello, world!
$ go build hello_go.go
$ ls
hello_go hello_go.go
$ ./hello_go
Hello, world!
$
Kotlin
fun main() {
println("Hello World!")
}
KotlinはAndroid公式のプログラミング言語なのだそうだ。2011年開発。WikipediaによるとJavaよりもKotlinの方が簡単にAndroidアプリを開発できるらしい。それ以上は知らない。
上記はAIが作成したプログラムである。
であるが、コンパイルエラーになった。
error: could not find or load main class hello_kotlin.kt
「main」クラスが必要?
面倒だ。コンパイルする方法もAIに教えてもらう。
AIとのやり取りは省略。
「-include-runtime」が必要だそうだ。
実行してみる。
$ ls
hello_kotlin.kt
$ cat hello_kotlin.kt
fun main() {
println("Hello World!")
}
$ kotlinc hello_kotlin.kt -include-runtime -d hello_kotlin.jar
$
$ ls
hello_kotlin.jar hello_kotlin.kt
$ kotlin hello_kotlin.jar
Hello World!
$
Perl
頑張れAI!
# PerlでHelloWorldを出力する
print "Hello, World!\n";
perl hello.pl
AI引用に絵文字が入っているが、これを入れたのもAIである。
$ ls
hello_perl.pl
$ cat hello_perl.pl
# PerlでHelloWorldを出力する
print "Hello, World!\n";
$ perl hello_perl.pl
Hello, World!
$
Ruby
# RubyでHelloWorldを出力する
puts "Hello, World!"
「puts」!
これはまた珍しい。
C言語の標準関数にも「puts」はあるが、「printf」よりも原始的な関数だ。C言語の「puts」関数は書式指定ができない。Rubyの「puts」で出力文字列の書式は指定できるんだろうか。あるいは、書式を指定するには別の関数があるのか。
Rubyは1995年にPerlに続く言語として発表された。開発者はまつもとひろゆき。日本発のプログラミング言語である。インタプリタであるがコンパイルも可能。
それにしても、printでもwriteでもなく、なぜpursなんだろう。
$ ls
hello_ruby.rb
$ cat hello_ruby.rb
# RubyでHelloWorldを出力する
puts "Hello, World!"
$ ruby hello_ruby.rb
Hello, World!
$
Scala
なんだコレは。もはや名前も知らない。
おーい、えーあーい。
// Scalaでhelloworldを書く
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
ん?
関数型?
関数型も書けるの?
じゃあ、関数型を勉強するときはこれで練習する?
$ ls
hello_scala.scala
$ cat hello_scala.scala
// Scalaでhelloworldを書く
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
$ scala hello_scala.scala
Hello, world!
$
Fortran
突如として古いコンパイラである。
だが、今でも現役だ。
program hello
print *, "Hello, world!"
end program hello
そう。かつては「数値計算にはFortran」と言われた時代があった。「事務処理はCobol」、「制御系がC言語」などと言われたものだ(  ̄- ̄)。
あれ?
でも・・・
Fortranって、全大文字じゃなかったっけ?
Fortran77だったかな。
あ、そうか。
PROGRAM HELLO
WRITE (*,*) 'Hello, world!'
STOP
END
あ、ほら、やっぱり、全大文字!
っていうか、なんか、随分変わってね?
こんだけ変わったプログラミング言語も珍しいんじゃあないか。
Fortran90のコンパイル、実行
「lfortran」で、「a.out」を生成するだけでなく実行もしてる。
$ ls
hello_f90.f90
$ cat hello_f90.f90
program hello
print *, 'Hello World!'
end program hello
$ lfortran hello_f90.f90
Hello World!
$ ls
a.out a.out.tmp.o hello_f90.f90
$ ./a.out
Hello World!
$
Fortran77は、「f2c」とやらで、なんと!C言語に変換するらしい。やってみたかったけど「f2c」が入手できなかった。ちょっと残念。
Cobol
だんだん、何をしているのかわからなくなってきた。記事を書いているのか、AIをコピペしているのか。
まぁ、いいや、たまには。
最後。
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "Hello, world!".
STOP RUN.
では、いただきましたコードをコンパイルする。
~/hello/cobol $ l
hello.cobol
~/hello/cobol $ cat hello.cobol
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "Hello, world!".
STOP RUN.
~/hello/cobol $ cobc hello.cobol
hello.cobol:1: error: invalid indicator 'F' at column 7
hello.cobol:2: error: invalid indicator 'M' at column 7
hello.cobol:3: error: invalid indicator 'U' at column 7
hello.cobol:4: error: invalid indicator 'S' at column 7
hello.cobol:5: error: invalid indicator 'O' at column 7
hello.cobol:6: error: PROGRAM-ID header missing
~/hello/cobol $
ん?
んん?
なに?
error?
'F' 'M' 'U' 'S' 'O' ?
全部「column 7」?
どゆこと?
あ、あ、あ。
行番号がいったっけ?
~/hello/cobol $ cat hello.cobol
000010 IDENTIFICATION DIVISION.
000020 PROGRAM-ID. HELLO.
000030 PROCEDURE DIVISION.
000040 DISPLAY "Hello, world!".
000050 STOP RUN.
~/hello/cobol $ cobc hello.cobol
~/hello/cobol $
ふん。
通った。
~/hello/cobol $ l
hello.cobol hello.so*
~/hello/cobol $
出来上がったのは、この「hello.so」なるものか。
では。
~/hello/cobol $ ./hello.so
Segmentation fault
~/hello/cobol $
はい?
せぐめんてーしょんふぉーると?
「DISPLAY "Hello, world!".」を実行中しようとしただけなのに、どうやったらそんなことになるのよ。
さあ、出番だよ、AI君。
あい、わかった。
「-x」を付けよう!
知らんけど。
~/hello/cobol $ cobc -x hello.cobol
~/hello/cobol $
コンパイル、OK。
~/hello/cobol $ l
hello* hello.cobol hello.so*
~/hello/cobol $
この「hello」というやつか。
いざ、実行。
~/hello/cobol $ ./hello
Hello, world!
~/hello/cobol $
あらまぁ、動いたわ。
「-x」って、何なんだ。
「-x」があるのとないのと、どう違う?
help!
それはわかった。
だから、「-x」がない時に出力される「hello.so」てな、何だ?
おい、AI!(だんだん、粗野になってきた)
共有ライブラリ!
デフォルトで出力するのがライブラリで、実行ファイルを出力するためにはわざわざ「-x」のオプションがいるということか? ほとんどのコンパイラは逆だと思うんだが。
どうでもいいが「情報やや機能」とは、何だ。
最初は誤字かと思ったが(AIが誤字!)2回もあると、いろいろと勘ぐってしまう。
いずれにしても「so」ファイルも「cobcrun」とやらで実行できるらしい。ここまで来たら試してみるか。
~/hello/cobol $ cobcrun hello.so
libcob: error: module 'hello.so' not found
~/hello/cobol $
は?
not found?
じゃあ、こうか?
~/hello/cobol $ cobcrun ./hello.so
libcob: error: module 'hello.so' not found
~/hello/cobol $
「hello.so」が、ない?
???
一筋縄ではいかない。
どうしたら解決できる?
しばらくAIを小突き回してみたんだが、どうにも埒が明かない。
結局、普通に検索して行き着いたのがココ。
いつもお世話になります「stackoverflow」さん。
質問内容が、もう、全く同じで。
同じ人がいるなんて、嬉しいじゃないか。
そんでもって、回答がこちら。
えーっと、なになに?
面倒じゃ。
対策は3つもあげてくれている。
1つ目。
「-x」をつけて「cobc」を実行する。
こうすればライブラリではなく、実行モジュールができる。それはさっきやった。次。
2つ目。
ファイル名と、PROGRAM-IDが異なる場合は、次のように呼び出す。
この場合、ファイル名が「hello_cob」で、
PROGRAM-ID.が「HELLO」なので、
「cobcrun -M hello_cob HELLO」で呼び出すことになる。
なんと、PROGRAM-ID.にそんな意味があるのか。
3つ目。
ファイル名とPROGRAM-IDを同じにする。
ファイル名を HELLO.so
PROGRAM-ID.も HELLO
そうすれば、
cobcrun HELLO
だけで実行可能となる。
なるほど。
以上、Termuxでインストール、実行できるコンパイラ達である。まだ他にもあるかもしれない。
私が若い頃はフリーでコンパイラを手にするなどということはできなかった。コンパイラがこれほどザクザクと簡単に入手できるなど、つい嬉しくなってあれもこれもとつつき回してしまう。本格的に使うものはわずかだろうが、いつでも使えるというのは、なんだかウキウキする。
さあ、いつでも、どこでも、プログラミングを勉強できますよ!