Java.next

次に採用する JVM 言語を選択する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Java.next

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Java.next

このシリーズの続きに乞うご期待。

私は詩人として失格です。おそらくどの小説家も同じだと思いますが、初めは詩を書こうとします。詩を書く才能がないと自覚すると、今度は詩の次に難しい形である短編小説に挑みます。それにも失敗したら、後は小説を書くしか道は残されていません。
—ウィリアム・フォークナー、ノーベル賞受賞作家

偉大な作家であったとしても、すべての作家があらゆる表現手段でものを書くとは限りません。それと同じように、プログラマーにとって、自分の性分に合っていると感じるプログラミング言語とそうでないものがあります。生まれながらの C 言語プログラマーもいれば、最初から Lisp を理解できる開発者、あるいは Perl に絶大な信頼を置く開発者もいます。すべての開発者の好みにぴったり合う言語は 1 つもないという事実が、これだけ多くのコンピューター言語が存在する理由を物語っています。Java.next に当てはめてみても、いずれか 1 つの Java.next 言語だけが、この Java.next 言語の世界を支配することはないでしょう。誰にとっても完璧な 1 つの言語というものはないからです。

Java 言語はその反例のように思えるかもしれませんが、Java が支配的な言語になった理由は、Bruce Tate 氏が彼の著書『Beyond Java』で「perfect storm (パーフェクト・ストーム)」と呼んでいる特有の状況があったからです。1990年代半ばにリリースされた Java は、その導入を巡って苦戦を強いられました。Java は、その頃よく使われていたコンパイル言語よりも速度に劣り、(メモリーの価格が一時的に急上昇していた当時は) メモリー不足であり、かつて優勢を占めていたクライアント/サーバー・スタイルの開発に特別適していたわけでもありませんでした。Java の取り柄は、(ガーベッジ・コレクションなどの機能により) 比較的簡単に使えること、そして当時は Java でしか実装されていなかったアプレットの 2 つだけです。この状況が変わらなかったとしたら、Java は生き残っていなかったでしょう。

けれども、Java とその当時新しく登場した World Wide Web は、最高の組み合わせでした。特に、Servlet API がよく使われるようになったこともあり、突如としてサーバー・サイドのデプロイメント・モデルが Java の欠点の多くを軽減することになりました。これらの要素 (ハードウェア、Web、パラダイム) が重なったことが、Tate 氏の言うパーフェクト・ストームです。開発者が Web をプログラミングするための新しいツールを必要とするなか、サーバー・サイドの Java はメモリーの制約を緩和し、堅牢な Web アプリケーションを構築するための簡素化されたモデルを提示しました。ちょうど良い場所でちょうど良いタイミングで存在し、しかも大手企業 (Sun) がサポートしたことから、Java はソフトウェア業界で支配的な力を持つようになったのです。

別の言語に、このように都合よく一連の状況が重なり合うことはまず考えられません。現在は複数のコンピューター言語が混在するようになっていて、この多言語の傾向は次第に高まりつつあります。Java と同じ影響を持つ次の言語を特定しようとしても、それは失敗する運命にあります。どの Java.next 言語を採用するかを調べるときには、圧倒的な人気を基準にするのではなく、自分に合うかどうかを焦点にする必要があります。

マルチパラダイム言語

最近の新しい言語の多くは、オブジェクト指向、メタプログラミング、関数型、手続き型など、複数のプログラミング・パラダイムをサポートしています。Java.next 言語のうち、Groovy と Scala の 2 つは、そのようなマルチパラダイム言語に該当します。Groovy は、ライブラリーによって関数型の拡張機能を追加するオブジェクト指向の言語です。Scala はオブジェクト指向と関数型のハイブリッド言語であり、不変性や遅延処理などの関数型プログラミングの傾向に重点を置いています。

マルチパラダイム言語は絶大な能力を持っており、パラダイムをミックス・アンド・マッチして問題に厳密に適合させることができます。バージョン 8 より前の Java には、多くの開発者を苛立たせる制約があります。Groovy のような言語は、メタプログラミングや関数型構成体をはじめ、Java よりも遥かに多くの機能を提供しています。

マルチパラダイム言語は強力とは言え、大規模なプロジェクトでは、より厳重な開発者の統制が求められます。マルチパラダイム言語は多種多様な抽象化と原則をサポートすることから、開発者グループがそれぞれに孤立していると、ライブラリーでまったく異なるバージョンが作成されることになりかねません。例えばコードの再利用は、オブジェクト指向の世界では構造というスタイル、関数型の世界では複合関数および高階関数というスタイルになる傾向があります。会社の顧客 API を設計するときには、最適なスタイルを決めて、チームの全員がそのスタイルに合意 (そして準拠) することが不可欠です。Ruby はマルチパラダイム言語であることから、Java から Ruby に移行した開発者の多くがこの問題に直面しました。同じくマルチパラダイム言語である C++ は、不適切に (そして通常は不注意に) 手続き型やオブジェクト指向に範囲を広げようとした多数のプロジェクトで問題を起こしました。

1 つの解決策は、エンジニアリングの規律に頼り、プロジェクトの開発者全員が同じ目標に向けて作業するのを確実にすることです。広範囲に影響する変更をコア・クラスに追加するために、メタプログラミングを使用することに慎重な開発者は少なくありません。そこで、例えばテスト用のライブラリーを使用して、アサーションを広範囲に適用できるようにするメソッドを Object に追加します。ユニット・テストを実行すれば、複雑な拡張を極めて正確に理解できるので、未知の副次作用に対する不安が軽減されます。

Clojure を含む一部の言語では、主に 1 つのパラダイムを採用する一方、実利的に他のパラダイムをサポートするという方法で、規律を強化しています。Clojure はあくまでも、JVM を対象とした関数型の LISP です。基礎となるプラットフォームからクラスやメソッドを操作 (そして、必要に応じて独自のものを作成) することはできますが、Clojure が第一にサポートするのは、不変性や遅延処理など、確固たる関数型パラダイムです。

決定的な特徴: 関数型プログラミング

ほとんどの開発者にとって、関数型プログラミングを採用することは、次に採用する言語の最も重要な特徴となります。Java.next 言語に伴う関数型の側面については、これまでこの連載のいくつもの記事で取り上げてきました。関数型パラダイムの有効性の鍵は、概念をより上位レベルの抽象化で表現できるところにあります。

Java.next: メモ化、そして関数型での相乗効果」の記事では、命令形の indexOfAny() メソッド (Apache Commons StringUtils ライブラリーに含まれるメソッド) を Clojure に変換した結果、短く簡潔ながらも、より汎用の関数になりました。Clojure は、最初から極めて読みやすくなるように設計されていますが、Lisp 以外の開発者にとっては奇異に見えます。一方、Scala は Java 開発者に理解しやすいように設計されています。同じ indexOfAny() メソッドを Clojure ではなく Scala にキャストすると、リスト 1 のようになります。

リスト 1. Scala での indexOfAny() の実装
def indexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Option[Int] = {
  def indexedInput = (0 until input.length).zip(input)
  val result = for (char <- searchChars; 
       pair <- indexedInput; 
       if (char == pair._2)) yield (pair._1)
  if (result.isEmpty) 
    None
  else 
    Some(result.head)
}

indexOfAny メソッドの目的は、2 番目の引数で渡された任意の文字が 1 番目の引数で渡された文字の中でどのインデックス位置にあるかを返すことです。リスト 1 では、最初に indexedInput を生成するために、入力ストリングの長さに基づく数値のシーケンシャル・リストを作成します。次に、Scala の組み込み zip() 関数を使用して 2 つのリストをまとめて zip します。例えば、入力ストリングが zabycdxx だとすると、indexedInput には、Vector((0,z), (1,a), (2,b), (3,y), (4,c), (5,d), (6,x), (7,x)) が含まれる結果となります。

indexedInput コレクションが生成された後は、for 包含を使用して、元のバージョンのネストされたループを置換します。それにはまず、「searchChars;」を検索します。つまり、indexedInput に含まれるペアの 2 番目の部分にこの文字が存在するかどうかをチェックして (Scala の省略形である pair._2) を使用)、その文字が (pair._1) に一致する場合は、ペアのインデックス部分を返します。戻りリストの値は、yield() 関数が生成します。

Scala では、null の可能性がある場合には、代わりに Option を返すのが通常です。従って上記では、結果が存在しなければ None を返し、存在すれば Some を返すようにしています。元の indexOfAny() メソッドは、最初に一致した文字のインデックスだけを返すため、結果の (result.head) には最初の要素だけを含めて返していますが、Clojure バージョンでは、すべての一致のリストを返します。Scala バージョンをそれと同じように変換するのは簡単です (リスト 2 を参照)。

リスト 2. すべての一致を返す indexOfAny
def lazyIndexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Seq[Int] = {
  def indexedInput = (0 until input.length).zip(input)
  for (char <- searchChars; 
       pair <- indexedInput; 
       if (char == pair._2)) yield (pair._1)
}

リスト 2 では、戻り値として、最初の一致だけでなく、すべての一致のリストが返されます。例えば、lazyIndexOfAny("zzabyycdxx", "by") の結果は、入力ストリングに含まれる対象文字それぞれのインデックスと一致する Vector(3, 4, 5) です。

関数型プログラミング言語では、ループよりも優先してもっと強力なビルディング・ブロック (map() など) を使用して、より上位レベルの抽象化で作業することができます。下位レベルでコードの詳細に対処する必要がなくなれば、より関連性の強い問題をより明確な対象にして取り組むことができます。

関数型のピラミッド構造

一般に、コンピューター言語で使用される型は、2 つの軸上に位置付けられます。1 つは、強い型付けであるか弱い型付けであるかを示す軸、もう 1 つは動的であるか静的であるかを示す軸です (図 1 を参照)。

図 1. 言語の型付けの特徴
言語の型付けの特徴を示す図
言語の型付けの特徴を示す図

強い型付けの変数の場合、変数はその型を「自覚」するため、リフレクションやインスタンス・チェックが可能になります。型の情報は変数に保持されます。弱い型付けの言語では、型をそれほど意識しません。例えば C は、静的で弱い型付けの言語です。C での変数は、実際にはビットの集まりであり、至るところにいる C 開発者にとって幸か不幸か (場合によっては、その両方)、これらのビットはさまざまに解釈することができます。

Java は、静的で強い型付けの言語です。変数を宣言するときには、変数の型を、場合によっては繰り返し指定しなければなりません。Java では徐々に型推論を導入するようになっていますが、型に関する簡潔さという点では、どの Java.next 言語にもまったく及びません。Scala、C#、F# も静的で強く型付けされますが、これらの言語は、型推論を使用することによって冗長さを大幅に抑えています。言語が適切な型を判別できることから、冗長さが軽減される場合はよくあります。

以上のような特徴はプログラミング言語の初期の頃からありましたが、この方程式に新しい側面が加わっています。それが、関数型プログラミングです。

Java.next: 関数型のコーディング・スタイル」で説明したように、関数型プログラミング言語の設計理念は、命令型プログラミング言語の設計理念とは異なります。命令型の言語は、容易に状態を変更できるようにすることを目指していて、これを目的とした多数の機能を組み込んでいます。一方、関数型の言語が目指しているのは、可変の状態を最小限にして、より汎用のメカニズムを構築することです。ただし、図 2 を見るとわかるように、関数型の言語は、型付けの体系には影響しません。

図 2. 関数型プログラミング言語
関数型プログラミング言語の中には、動的に型付けされる言語も、静的に型付けされる言語もあることを示す図
関数型プログラミング言語の中には、動的に型付けされる言語も、静的に型付けされる言語もあることを示す図

関数型プログラミング言語は、不変性を利用します (場合によっては、不変性が必須です)。言語を差別化する重要な要因は、今や、動的であるか静的であるかではなくなっています。現在の決定的な差別化要因は、命令型であるか、関数型であるかです。このことは、ソフトウェアを作成する方法に関して興味深い意味を持ちます。

2006年の私のブログの中で偶然に、「多言語プログラミング (polyglot programming)」という言葉を再び広めて、新しい意味を与えました。それは、最近のランタイムを利用して、プラットフォームではなく言語をミックス・アンド・マッチするアプリケーションを作成するという意味です。このように再定義したのは、Java および .NET プラットフォームが合わせて 200 を超える言語をサポートしていることに気付いたこと、そしてあらゆる問題を解決できる「ただ 1 つの真の言語」は存在しないのではないかという疑念が生じたことにあります。最近の管理されたランタイムでは、バイトコード・レベルで自由に言語をミックス・アンド・マッチして、特定のジョブに最も適した言語を使用することができます。

このブログ記事を公開した後、同僚の Ola Bini が、彼の考える多言語ピラミッド構造について説明するフォローアップ投稿を公開しました。このピラミッド構造 (図 3 を参照) は、多言語世界でアプリケーションを構造化する場合に推奨される方法を示しています。

図 3. Bini のピラミッド構造
Ola Bini のピラミッド構造

Bini がこの逆さのピラミッド構造で提案している方法では、信頼性が何よりも優先される最下層では、より静的な言語を使用します。次のアプリケーション層には、それよりも簡潔な構文でユーザー・インターフェースのようなものを作成できる、より動的な言語を使用します。そして一番上の層で、重要なドメインの知識とワークフローを簡潔にカプセル化するために開発者が作成した、ドメイン特化言語 (DSL) を使用します。カプセル化において DSL の機能を利用するために、通常、DSL は動的言語で実装されます。このように、Bini が目標としたのは、基礎のレベルでは確実性に重点を置き、頂上に近づくにつれ、重点を柔軟性に移していくことです。

Bini のピラミッド構造は、当初の私の投稿に極めて深い洞察を加えたものです。けれども、その後の数年の間に、言語の全体的な状況は変化しました。私は現在、型付けは以前よりも開発者の好みの問題となっていて、関数型か、命令型かという重要な特徴から注意をそらすものだと考えています。図 4 に、私が新しく作成した関数型のピラミッド構造を示します。

図 4. 関数型のピラミッド構造
Neal Ford が作成した関数型のピラミッド構造
Neal Ford が作成した関数型のピラミッド構造

私が切望するレジリエンシーは、静的な型付けからもたらされるのではなく、最下部で関数型の概念を採用することによってもたらされます。力仕事 (データ・アクセスや統合など) に対応するコア API のすべてが、不変性を前提とすることができれば、すべてのコードは大幅に簡潔になるはずです。もちろん、至るところにある不変性によって、データベースやその他のインフラストラクチャーを作成する方法は変わってきますが、結果としてはコアの安定性がより高まることになります。

関数型のコアをベースに、ワークフロー、ビジネス・ルール、UI、そして開発の容易さが優先されるその他のシステムの部分については命令型言語で処理します。元のピラミッド構成と同じく、頂点では DSL が同じ目的を果たします。ただし、DSL は、システムの上から下までのすべての層にわたって使用されるはずです。このことは、Scala (静的で強い型付けの関数型言語) や Clojure (動的で強い型付けの関数型言語) などの言語では、簡潔な方法で重要な概念を取り込むために、簡単に DSL を作成できることが裏付けています。

このピラミッド構造に従ってアプリケーションを構築するのは大きな変化を意味しますが、この変化には魅力的な結果が伴います。可能性の一端を知るには、Datomic (市販用の製品) のアーキテクチャーを調べてください。Datomic は、あらゆる変更の履歴を完全な忠実度で維持する関数型のデータベースです。更新によってデータが壊れることはありません。更新すると、新しいバージョンのデータベースが作成されます。過去のスナップショットを確認するために、データベースをロールバックすることもできます。常に履歴を使用できるため、例えば、時間面でデータベースをロールバックおよびロールフォワードできる機能を利用した、継続的ソフトウェア・デリバリーなどの実践が、簡単にできるようになります。また、スキーマやコードの変更を直接同期することができるため、アプリケーションの複数のバージョンをテストするのも簡単です。Datomic は Clojure で作成されていて、アーキテクチャー・レベルで関数型の構成体を使用しています。これにより、スタックの最上部には素晴らしい影響がもたらされます。

まとめ

この記事は、連載「Java.next」の最終回です。この連載がきっかけとなって、皆さんがこれまでの 15 回の記事で紹介した Java.next 言語の概要とその概念をさらに深く調べてみる気になったことを願います。プログラミング言語の領域は、18 ヶ月前にこの連載を開始してから様相が変わっています。Java 8 がついに関数型プログラミング言語のコア要素を追加して、Java.next 分野の強力な競争相手になっています。今後数年の間は、Java 8 が言語全体のなかで優勢を占めることでしょう。4 つの Java.next 言語 (Groovy、Scala、Clojure、Java 8) には、いずれも強力なコミュニティーと拡大し続けるユーザー・ベースがあり、常に新しいイノベーションが生まれています。皆さんがどの言語 (または言語の組み合わせ) を選択するかに関わらず、JVM 言語の見通しは明るく見えます。


ダウンロード可能なリソース


関連トピック

  • Beyond Java』(Bruce Tate 著、O'Reilly Media、2005年刊): Java 言語を優位に導いた「パーフェクト・ストーム」について読んでください。
  • Scala: Scala は JVM 上で実行される最近の関数型言語です。
  • Groovy は、Java 言語の動的バージョンとして Java の構文と機能が更新されたものです。
  • Clojure: Clojure は JVM 上で実行される最近の関数型 Lisp です。
  • Functional Thinking』(Neal Ford 著、O'Reilly Media、2014年): この連載の著者が執筆した本で、関数型プログラミングについて詳しく学んでください。
  • Polyglot Programming」: Neal Ford の2006年のブログ投稿を読んでください。
  • Fractal Programming」: フラクタル・プログラミングと多言語ピラミッド構造に関する Ola Bin のブログ投稿を調べてください。
  • Datomic: Datomicのアーキテクチャーを参照してください。これは、Clojure で作成された不変のデータベース・サーバーです。
  • developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した豊富な記事を調べてください。
  • IBM SDK, Java Technology Edition Version 8: IBM SDK for Java 8.0 ベータ版プログラムに参加してください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=977868
ArticleTitle=Java.next: 次に採用する JVM 言語を選択する
publish-date=07242014