Javaプログラミング言語が当初直面した制約は、今日の開発者が直面する状況とは異なっていました。具体的には、1990年代半ばのハードウェアの性能とメモリの制限により、Java言語にはプリミティブ型が存在していました。それ以来、Java言語は進化し、オートボックス化によって多くの厄介な操作を排除し、次世代の言語はさらに進化し、各言語の矛盾や衝突を排除してきました。
今回は、次世代言語が、構文的にもデフォルトの動作の点でも、Javaの一般的な制限をどのように取り除くことができるかを紹介します。*** 制限の1つは、ネイティブ言語のデータ型の存在です。
原語の終焉
Java言語は、8組のプリミティブとそれに対応する型ラッパー・クラスから始まり、オートボックス化によって徐々に違いを薄めていきました。
Groovyはプリミティブ型を完全に隠します。例えば、intは常にIntegerを表し、Groovyは数値のオーバーフローエラーを防ぐために数値型のアップコンバージョンを自動的に処理します。例えば、リスト1のGroovyシェルのインタラクションを見てください:
リスト1.Groovyによるプリミティブの自動処理
groovy:000> 1.class
===> class java.lang.Integer
groovy:000> 1e12.class
===> class java.math.BigDecimal
リスト1では、Groovyシェルが、定数さえも基礎となるクラスによって表現されていることを示しています。すべての数値は実際にはクラスなので、メタプログラミングのテクニックを使うことができます。これらのテクニックには、数値にメソッドを追加することや、3.cmのような式をサポートすることが含まれます。この機能については、後ほど拡張性を取り上げる号で詳しく説明します。
Groovyのように、Clojureは自動的にプリミティブとラッパーの区別を覆い隠し、メソッド呼び出しがすべての型で実行されるようにし、ボリューム型変換を自動的に処理します。多くの場合、コンパイラがより高速なコードを生成できるようにする型を提供できます。例えば、(defn sum[x] ...) を使ってメソッドを定義する代わりに、(defn sum[x] ...) を使ってメソッドを定義できます。 )を使ってメソッドを定義する代わりに、型ヒントを追加することができます。
Scalaはまた、プリミティブ間の区別を遮蔽し、通常、コードの時間に敏感な部分には基礎となるプリミティブを使用します。また、2.toStringのように、定数に対してメソッドを呼び出すこともできます。プリミティブと Integer などのラッパーを混ぜて使用できるため、Scala は Java のオートボックスよりも透過的です。たとえば、Scala の == 演算子は、Java バージョンの同演算子とは異なり、プリミティブとオブジェクト参照で正しく機能します。また、Scala には eq メソッドも含まれており、基礎となる参照型の等価性を常に比較します。基本的に、Scala はデフォルトの動作をインテリジェントに切り替えます。Java では == が参照を比較しますが、これはほとんど必要なく、値の比較には直感的でない equals() を使用します。Scalaでは、基礎となる実装に関係なく==が正しく機能し、あまり一般的ではない参照の等価性チェックを実行する方法も提供します。
Scalaのこの機能は、Javaの次世代言語の主な利点の1つが、低レベルの詳細を言語とランタイムにオフロードすることで、開発者がより高レベルの問題を考えることに時間を割けるようになることを示唆しています。
デフォルト動作の簡素化
Java言語の一般的な操作には構文が多すぎるという点については、多くのJava開発者が同意しています。たとえば、プロパティ定義やその他のサンプル・コードはクラス定義を乱雑にし、重要なメソッドを不明瞭にしています。Javaの次世代言語はすべて、作成とアクセスのプロセスを簡素化する方法を提供しています。
Scalaのクラスとケースクラス
Scalaではクラス定義が単純化されており、アクセス関数、代入関数、コンストラクターが自動的に作成されます。例えば、リスト 2 の Java クラスを見てください:
リスト2.JavaのシンプルなPersonクラス
リスト 2 でサンプルコードでないのは、書き直した toString() メソッドだけです。コンストラクターとすべてのメソッドはIDEによって生成されます。コードを素早く生成することよりも、後でコードを簡単に理解することの方が重要です。無駄な構文は、根本的な意味を理解するために使わなければならないコードの量を増やします。
Scala Person クラス
驚くべきことに、Scala で書かれたリスト 3 のシンプルな 3 行の定義では、同等のクラスが作成されます:
リスト3 Scalaの同等クラス
リスト3のPersonクラスは、ミュータブルなageプロパティ、イミュータブルなnameプロパティ、2つの引数を持つコンストラクタ、そして私が書き直したtoString()メソッドに凝縮されています。興味深い部分が構文に埋もれていないので、このクラスのユニークな点を見るのは簡単です。
Scala の設計では、最小限の構文でコードを作成できることが重視されており、構文の多くが省略可能になっています。リスト 4 の単純なクラスは、文字列を大文字に変更する Verbose クラスを示しています:
リスト4: Verboseクラス
リスト4のコードの多くはオプションです。リスト5では、クラスの代わりにオブジェクトを使用して、同じコードを示しています:
#p#
リスト5.大文字に変換された、より単純なオブジェクト。
Java の静的メソッドに相当する Scala のコードでは、クラスの代わりにオブジェクトを作成することができ、リスト 5 からは、1 行メソッドの本体を区切るメソッドの戻り値の型と、リスト 4 の無駄な s 引数が消えています。リスト 5 からは、メソッドの戻り値の型、1 行メソッドの本体を区切るための括弧、そして の役に立たない s 引数がすべて消えています。 Scala のこの「折りたたみ可能な構文」には、メリットとデメリットの両方があります。折りたたみ可能な構文を使用すると、非常にイディオム的な方法でコードを記述できるようになりますが、素人にはコードを理解するのが難しくなります。
ケースクラス
データ・ホルダーとして使用される単純なクラスは、オブジェクト指向システム、特に異なるシステムと通信しなければならないシステムでは一般的です。この種のクラスの人気により、Scalaプロジェクトは、自動的にさまざまな便利な構文を提供する.caseクラスの作成で一歩前進することができました:
- ファクトリーメソッドは、クラスの名前に基づいて作成できます。例えば、new キーワードを使わずに新しいインスタンスを作成することができます。
- このクラスの引数リストに含まれるパラメータはすべて自動的に val になります。
- コンパイラは、クラスに適切なデフォルトの equals() メソッド、hashCode() メソッド、および toString() メソッドを生成します。
- コンパイラはクラスに copy() メソッドを追加し、 バリアント形式の変更を行うためにコピーを返すことができるようにします。
Javaの次世代言語は、構文上の欠陥を修正するだけでなく、現代のソフトウェアがどのように動作するかについてのより正確な理解を促進し、その方向にツールを形成します。
Groovyの自動生成プロパティ
次世代のJava言語の中で、GroovyはJavaの構文に最も近く、一般的な状況に対して「構文シュガー」と呼ばれるコード生成メソッドを提供します。リスト6の単純なGroovy Personクラスを見てください:
リスト6.Groovy Personクラス
リスト6のGroovyコードでは、フィールドdefを定義すると、アクセス関数と代入関数が生成されます。これらの関数のうち1つだけを使いたい場合は、nameプロパティに対して行ったように、自分で定義することができます。メソッドの名前はgetName()ですが、より直感的なbob.name構文でアクセスできます。
Groovyでequals()とhashCode()メソッドのペアを自動生成したい場合は、@EqualsAndHashCodeアノテーションをクラスに追加します。このアノテーションは、Groovyを使用して属性に基づいてメソッドを生成します。デフォルトでは、このアノテーションはプロパティのみを考慮します。includeFields=true修飾子を追加すると、フィールドも考慮します。
Clojureのマッピング記録
Clojureで他の言語と同じPersonクラスを作成することは可能ですが、これはイディオムではありません。伝統的に、Clojureのような言語は、この種の情報を保持するためにデータ構造に依存しており、その構造を扱う多くの関数を使用してきました。マッピングで構造化データをモデル化することはまだ可能ですが、より一般的なシナリオは、レコードを使用することです。レコードは、Clojureのより正式な属性付き型名のカプセル化であり、各インスタンスは同じ意味的な意味を持ちます。
例えば、次のような人事の定義を考えてみましょう:
この構造を考えると、ageは(get mario :age)でアクセスできます。単純なアクセスはマッピングに対する一般的な操作です。Clojureでは、より効率的な(:age mario)省略記法を使用するために構文的な糖を利用することが可能で、Clojureはマッピングを操作することを期待しているので、この操作を単純化するために多くの構文的な糖を提供します。
Clojureには、リスト7に示すように、ネストされたマッピング要素にアクセスするための構文シュガーもあります:
Clojureへの速記アクセス
、halという入れ子のデータ構造を定義しました。外側の要素へのアクセスは期待通りに実行されます ((:fname hal))。 リスト7の 最後の行に示されているように、Lisp構文は「インサイド・アウト」評価を実行します。まず、halから住所レコードを取得し、次にcityフィールドにアクセスします。インサイドアウト」評価は一般的な使用法であるため、Clojureは式をより自然で読みやすくするために、式を反転させる特別な演算子を提供します: .
リスト8に示すように、レコードを使用して同等の構造を作成できます:
リスト8 レコードを使った構造体の作成
リスト8では、defrecordを使用して同じ構造を作成し、より伝統的なクラス構造になりました。Clojureでは、おなじみのマッピング操作と方言によって、レコード構造でも同じように簡単にアクセスできます。
Clojure 1.2は、2つのファクトリー関数を介して、一般的な操作のレコード定義の周りに構文的なシュガーを追加します:
- -> 型名、受信フィールドの位置パラメータ
- ->マッピング->タイプ名、フィールド値のキーワードマッピング
リスト8からバージョン9へのコード変換には、言語に適した関数を使用します。
リスト9 Clojureの素敵な構文糖。
多くの場合、レコードはマップやフラット構造よりも好まれます。まず、defrecordはJavaクラスを作成し、マルチメソッド定義で使いやすくします。次に、defrecordはより多くのタスクを指定し、レコードを定義する際にフィールドの検証やその他の細かい処理を可能にします。第三に、特に既知のキーのセットが決まっている場合、記録はずっと速くなります。
Clojureは、レコードの使用とコードの構築を組み合わせています。将来の回では、これらの関係を取り上げます。
結語
GroovyとScalaはクラスと共通ケースの構築を容易にし、Clojureはマッピング、レコード、クラスをシームレスに相互運用します。Javaの次世代言語に共通するテーマは、不要なサンプルコードを排除することです。次回は、このテーマを探求し続け、いくつかの例外について説明します。