あざらしとペンギンの問題

主に漫画、数値計算、幾何計算、TCS、一鰭旅、水族館、鰭脚類のことを書きます。

Groovy は Java Script であるか?

タイトルに深い意味はありませんが、ある人々にとって不快な表現ではあるかもしれません。このタイトルにおいて重要なのは、Java と Script の間に入れたスペースが意図したものであることです。

JavaJavaScript(スペースを入れない)について

2017年6月24日、私たちが生きているこの地球には、JavaJavaScript いうプログラム言語が存在します。百万回言われてきたこととして、JavaJavaScript は全く別の言語である、ということがあります。ある人はこれを「インドとインドネシアくらい違う」と表現します。現状は確かにその通りです。しかし、全くの偶然で似たような名前になったわけではありません。インドネシアがインドありきで付けられた名前であるように、JavaScriptJava ありきで付けられた名前なのです。これは、JavaScript を開発した Netscape Communications 社が、ウェブブラウザの開発において Java を開発した Sun Microsystems 社と業務提携したことに由来します。すなわち、JavaScript は意図的 Java に文法を似せて作られたのです。もしこの関係が存在していなかったならば、JavaScript となるはずだった言語の文法は全く異なったものになっていたと思われます。その経緯については Wikipedia などを参照してもらった方が早いでしょう。なお、Netscape CommunicationsSun Microsystems 両社とも、既に存在していないことは敢えて言うことでもありません。(と言いつつ言っている

そもそもスクリプトとは?

script という単語を辞書で引くと、手書き文字、手稿、原稿、台本、脚本、などといった意味が並びます。コンピュータの文脈においてどの意味を取るかは問題ですが、概ね「作業の流れを書いたもの」という意味で使われていると思われます。ところで、program を辞書で引くと、計画、予定、予定表、番組、といった意味が並ぶのですが、コンピュータの文脈ではやはり「作業の流れを書いたもの」と解釈するのが良さそうです。つまり、意味的にスクリプトとプログラムの間に明確な区別はないということです。

とまぁそういうわけにもいかないので、コンピュータにおけるスクリプトの意味をもう少し狭めると、シェルスクリプト、あるいは特定のアプリケーションを制御するコマンド群、という意味で使われることが多いようです。例えば、JavaScript は元来ウェブブラウザを操作するための言語でした。まとめると、機械を制御するのがプログラムで、それらプログラムを外部的あるいは内部的に制御するものがスクリプトであると言えます。しばしば同様の意味で使われる言葉としてマクロがあります。

スクリプトを記述するある種のプログラム言語を「スクリプト言語」と呼ぶことにしましょう。スクリプト言語は、何らかのアプリケーション上で実行されることが意図されているため、一般的なプログラム言語よりも「高級」な言語であると言えます。スクリプトが処理する主な対象は、基本的に人が目で見て意味が解るようなデータ、得には文字列です。また、どちらかといえば単純な処理を実行する目的で使われるものです。そのような目的のため、多くのスクリプト言語は次のような特徴を備えています。

  • コマンドをそのまま順に書くだけで実行できる。メイン関数などは不要
  • 変数を宣言しなくてもいきなり使える
  • 文字列を操作するためのコマンドを多く備えている
  • 基本的なデータ構造(リスト、キュー、ハッシュテーブルなど)を構文レベルで備えている

大雑把にまとめると

「とにかく単純な処理を手っ取り早く記述するのに向いている」

というのがスクリプト言語の特徴であり、元の目的にもかなっていると言えるでしょう。

文字列操作を主目的とする言語としては、Unix では古くから sedawk といったものがありましたが、重大な転換点は 1987 年に perl という言語が登場したことです。Perl はより強力な文字列処理機能を提供し、かつプログラム言語が進化の過程で獲得した様々な概念を取り込んだ、それ自体がプログラム言語として十分な能力を備えたスクリプト言語として広く普及しました。そして、インターネットが人口に膾炙するようになる頃には、PerlCGI の記述言語の主流となりました。もちろん Perl は上に挙げた4つの特徴を満たしています。また、Perl は後に現れる多くのプログラム言語に影響を与えました。1990 年代初頭に現れた PythonRuby、そして JavaScript がその例になっています。

さて、ここまでは「スクリプト言語」の持つ特徴を説明してきましたが、何をもってスクリプト言語と言うのかという問いに答えてはいません。最初の段落の通り、あるプログラム言語がスクリプト言語であるかそうでないかを明確に定める基準はありません。そこで、この記事の中では、

「とにかく単純な処理を手っ取り早く記述するのに向いている」ならばスクリプト言語と見なしていいだろう

というポリシーを取ることにします。

今回の話はタイトルの通り "Java Script" に関するものなので、JavaScript はこれ以上出てきません。あしからず。

Java のおさらい

Java を書いたことがある人は当然ご存知でしょうが、Java システムにおいては、ソースコードコンパイラによって CPU に依存しない中間コード(バイトコード)に変換し、それを Java 仮想機械(JVM)というプログラムに読み込ませて実行します。JVM はその名の通り Java システムにおける実質的な機械として働きます。別の言い方をすれば JVM は存在しない機械のエミュレータです。この方法の利点は、単純なインタープリタと違って実行時にソースコードの文字列処理を行わないため高速に実行できることと、本物の機械の間の様々な違いを JVM が吸収して(Java の開発者が主張するには)あらゆる機械の上で同じ動作をさせることができることがあり、無論重要なのは後者です。欠点としては、本物の CPU との間に JVM が入ることでネイティブコードに対して実行速度で劣ること、それ以前に JVM の起動のオーバーヘッドがあって目的のプログラムを実行するまでに時間がことなど、PC が現在よりはるかに貧弱だった頃にはかなり目立つものがありましたが、PC の高性能化と JVM の改良によって現在は実用上ほとんど問題にならないでしょう。

先に述べたようにバイトコードさえあれば、JVM はそれを読んで実行することができます。言い換えれば、バイトコードを生成する前のことは JVM にとって知ったことではありません。このような構成のために、Java システムは Java という単一の言語で書かれたプログラムの実行のみならず、バイトコードに変換するコンパイラさえ提供されていれば他の言語で書かれたプログラムの実行も許容する、言語に依存しないシステムであると言えます。矛盾しているように聞こえるでしょうか?

しかし実際には JVM で実行されることを前提とした言語とは既にいくつもあり、例えば Clojure, Scala, Kotlin などがそうです。また、いくつかの既存言語に少しの修正を加えて JVM で実行できるようにしたものもあります。

これらの言語の特長としては、Java のクラスなどをほとんどそのまま利用できることがあります。Java は標準 API として多くの機能を含んだクラスライブラリを備えています。他にもサードパーティ製のパッケージが数多く提供されています。(それらのほとんどは Maven リポジトリに登録されていて、Maven、Gradle、SBT などのビルドツールでは、依存関係を書いてやれば自動的に取ってきてくれます。)豊富にある Java の資源を利用できることは、その言語に大きな優位性を与えます。

Groovy とはなんぞや?

ここで時を同じくして、地球には Groovy というプログラム言語が存在します。これも先に上げたものと同じく JVM で実行されることを前提とした言語です。

Groovy が他の JVM を利用する言語と大きく異なるのは、それが Java そのものを含めることができるという点です。すなわち、Java のコードはそのまま Groovy のコードであり、Groovy は Java の拡張になっているのです。

Groovy のコードは Java そのものより短く簡潔なものとなります。いくつもの便利な文法を既存のスクリプト言語から拝借しているので、Groovy をそれらのスクリプト言語と同じくらい楽してプログラムを書くことができます。また、Groovy Shell という REPL シェルを備えていますので、ファイルを作らずとも試してみることもできます。ここで今回のタイトル「Groovy は Java Script であるか?」を回収しました。要するに Groovy は Java を簡単に記述できるスクリプト言語なのです。(と言い切ってしまうと各方面から怒られそう

Groovy は多くのスクリプト言語と同様に、変数宣言、型宣言なしにいきなり変数を使うことができます。また、標準的なクラス群は最初からインポートされているので、いちいち接頭辞をつけなくても使えるという特長もあります。これらも Groovy が Java を簡単に書けるということの理由です。

HelloWorld の比較

まずはこれでしょう。ご存知の通り Java では次のように書きます。ある意味で最も有名なコピペの一つと言えるでしょう。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

Java ではこれを HelloWorld.java というファイルに保存しなければなりません。ファイル名がクラス名と違うとコンパイル時に怒られます。とりあえずはコピペして実行してみましょう。すると次のように出力されるはずです。

$ javac HelloWorld.java

$ java HelloWorld
Hello, world!

Java はこのくらいにして Groovy ではどうなるかを見ていきます。まずは同じファイルを別名で保存しましょう。名前は何でも構いませんが、拡張子として .java を使うことはできません。ここでは hw.groovy としておきます。.groovy は Groovy のソースファイルを表すのに一般的に使われる拡張子ですが、少々長いのが難点です。まぁそれは置いといて、ともかく実行してみましょう。

$ groovy hw.groovy
Hello, world!

成功すればこのように表示されます。groovy は Groovy のソースファイルを実行するインタープリタです。先に言ったように Java のコードは Groovy のコードでもあるので、同じ結果になるはずです。ただし、拡張子を変えなければならないことには注意してください。

Groovy においては、行末のセミコロンは必須ではありません。よって、先のコードを

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!")
    }
}

としても怒られることはありませんし、結果も変わりません。セミコロンは同じ行に複数の文を書くときに使う程度です。

さて、これでは Groovy を使うメリットはほとんどありませんね。では hw.groovy を次のように書き変えてみましょう。

System.out.println("Hello, world!")

これを実行すると先と全く同じ結果になるはずです。要するに Groovy はクラスを書くことを強要せず、main メソッドがなければ単純にスクリプトとして実行します。

もっと簡単にしましょう。括弧も取っ払ってしまって結構です。

println "Hello, world!"

これでいいのだ!ここまでくると他のスクリプト言語と比較して遜色はないでしょう。

最後に今までのものを混在させてみましょう。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

System.out.println "Hello, world!!"

println "Hello, world!!!"

実行結果は次のようになるはずです。

$ groovy hw.groovy
Hello, world!!
Hello, world!!!

おや?ひとつ足りませんね。メインクラスをスクリプトの中に書いた場合、そのままでは HelloWorld の main 関数が呼ばれないのでこうなります。groovyc でコンパイルしてみると判るのですが、hw.groovy に書いたスクリプトの部分は、実際には hw というクラスに自動的に付加された main 関数から実行されます。そして、HelloWorld クラスは別のクラスとして分離されます。そのため、最初の Hello, world! を表示するには、 HelloWorld クラスの main 関数を明示的に呼ぶ必要があります。その方法はクラス定義の後に次の文を足すだけです。

HelloWorld.main null

そうするとちゃんと最初の奴も出ます。

$ groovy hw.groovy
Hello, world!
Hello, world!!
Hello, world!!!

最後に、1行のコードはコマンドラインから直接実行することができます。この場合はクォートのようなシェルで特別扱いされる文字に気をつける必要があります。(次の例では括弧なしでは正しくパースされないようなので敢えて括弧つきで書きました。)

$ groovy -e 'println("Hello, world!")'
Hello, world!

Groovy は GRuby?

Java はここまでとして、それ以外の文法面では、Groovy は Ruby の影響を強く受けています。そういえば名前も似ていますね。例えば、1 から 10 までの数を出力するには、Ruby

1.upto(10) { |x| p x }

と書くところを、Groovy では

1.upto(10) { x -> println x }

と書くことができます。正確には、Groovy の {} はクロージャラムダ式)を作る記号です。Groovy のクロージャは暗黙的に it というひとつの引数を受け取ることができるので、より簡単に

1.upto(10) { println it }

と書くこともできます。

似たようなものに JRuby という言語(?)もありますが、そちらはあくまで Ruby をほとんどそのまま Java システムに組み込むことを目的としているという点で、Groovy とは向いている方向が異なります。

変数宣言はいらない

多くの動的言語と同様に、Groovy では変数宣言をしなくても変数を使うことができます。例えば、Java では

int a = 1;
String b = "abc";
MyClass c = new MyClass();

と変数宣言しなければならないところを、

a = 1
b = "abc"
c = new MyClass()

と書くことができます。(前述の通り、文末のセミコロンはあってもなくても構いません。)

もちろん、Java と同じ形式での変数宣言を行うこともできます。これは Java のコードを Groovy のコードの中に貼り付けるのに何の修正もいらないということです。

変数宣言を書かないでもよいというのは、スクリプトを手早く書くためには非常にありがたいことです。また、Java で格好悪いとよく言われる、new の左右に同じクラス名が並ぶという見た目上の問題も解決されます。

とまぁ、変数宣言を書かないでいいことには長所、短所ともあるので、ここで議論することではないでしょう。

def による宣言

一方で、Groovy は Java とは異なる変数宣言の手段も提供しています。それは、キーワード def を使って行うものです。例えば先の3つの変数は、次のように宣言することができます。

def a = 1
def b = "abc"
def c = new MyClass()

これらの宣言の意味は、変数をその位置でのバインディング(変数の名前と実体を関連付ける表みたいなものだと思っておけばいいです)に登録するということです。一度登録された変数を同じバインディングで宣言することは禁止されます。例えば、

def a = 1
def a = 2

はエラーになります。

変数宣言は Groovy をスクリプトとして使う場合はあまり必要ないでしょうが、クラス内で変数を用いる場合には、一時変数であっても変数宣言が強制されます。

関数宣言も def

def の最も重要な機能は、関数やクラスのフィールド(メンバ変数)およびメソッド(メンバ関数)を定義することです。例えば、引数を2倍する関数は

def f(x) { x * 2 }

と書けます。返り値は最後に評価された値、もしくは return 文で返された値となります。よって、このように return 文を書かないで済ませることができます。

変数宣言と関数におけるバインディングに関して注意すべきことがあります。それは、スクリプトの地の文と関数内部とでバインディングが異なるということです。例えば、

a = 2
def f(x) { x * a }

は通りますが、

def a = 2
def f(x) { x * a }

はエラーとなります。

型はチェックされない

とまぁ、これまでは Groovy の良さについて述べてきましたが、やはり弱点もあります。それは、Groovy が動的型付け言語であることに由来するものです。

先程、変数宣言を Java と同じ形式で行うことができると言いましたが、Groovy はコンパイル時に型のチェックを行いません。よって、例えば int として宣言された変数に String 型の変数を代入するなどということができてしまいます。

実際にどうなるかを見てみましょう。まずは「次のコードをコピーして、Example1.java と Example1.groovy の別々のファイルに保存してください。

public class Example1 {
    public static void main(String[] args) {
        int a = 1;
        a = "abc"
        System.out.println(a);
    }
}

まずは Java の方をコンパイルしてみましょう。

$ javac Example1.java

コンパイラはおそらく「String は int に変換できない」と怒ってくるでしょう。それもそのはず、int 型で定義された変数に String 型の値を入れるのは見るからに無理です。(暗黙的にキャストもできません。)

一方、groovy で同様のことを試みると、

$ groovyc Example1.groovy

おそらく何のエラーも出さずに終わることでしょう。すなわち、コンパイル時に型チェックは行われていないのです。その文を実行したときに初めて「String は int に変換できない」と怒ってくるわけです。

コンパイルしてから実行するには

ところで、groovyc の結果は javac と同様に class ファイルとして出力され、

$ java Example1

のように JVM 上で実行することができます。ただし、クラスパスとして Groovy 本体のクラス群が置かれている場所、あるいはその jar ファイルをコマンドライン引数か環境変数(例えば CLASSPATH=".:/usr/share/groovy/lib/*" )で指定する必要があります。クラスパスに作業ディレクトリ(大体 '.')を入れることを忘れないでください。

まとめ

Groovy は JVM 上で実行されることを前提とした言語の中でも、Java そのものを含めることができることから、「楽に書ける Java」という使い方ができることを特徴とする言語です。対話型シェルとして groovysh が備わっているので、RubyPython と同様にコマンドを試してみることができます。また、groovyc によって全体をコンパイルしてから実行することもできます。

Groovy は Java の拡張であり、また多くの「スクリプト言語」と呼ばれるプログラム言語(特に Ruby)と類似した簡便な文法を備えていることから、

Groovy は Java Script である

と肯定的にタイトルを回収しました。