小さな紹介
みなさんこんにちは! 私はよくMediumに夢中になり、外国の開発者からの有益な記事をたくさん見つけます。 最近、私は自分でKotlinのDSLを探していて、KotlinのDSLとその操作方法に関する一連の記事に出会いました。 読む前に、DSLに出会うことはほとんどなかったので、DSLの表面的な概念がありました。 記事を読んでいる間、著者の例の説明と提示の単純さが気に入ったので、読み終わった後、この記事を翻訳することにしました。 もちろん、著者の承認を得て:)さて、始めましょう。
DSLについて簡単に説明します。 これは何ですか
最初に、ウィキペディアから定義を覗くことができます:
ドメイン固有言語(DSL)は、特定のアプリケーションに特化したコンピューター言語です。 多くの分野で広く使用されている汎用言語(GPL)とは異なります。基本的に、DSLはアプリケーションの特定の部分に焦点を当てた言語ですが、一方で、KotlinやJavaなどの汎用言語はアプリケーションの他の部分で使用できます。 SQLなど、おそらく既にご存知のDSLがいくつかあります。 SQLを見ると、英語の通常の文のように見えるため、非常に読みやすく 、 理解しやすく 、 表現力に 優れていることがわかり ます 。
SELECT Person.name, Person.age FROM Person ORDER BY Person.age DESC
DSLと通常のAPIを区別する特別な基準はありませんが、多くの場合、1つの違いに気づきます。特定の構造または文法を使用することです。 これにより、開発者だけでなく、プログラミング言語に精通していない人にとっても理解しやすい人にとってコードがより理解しやすくなります。
Kotlinを使用したDSL
では、Kotlin言語の機能を使用してDSLを作成する方法と、それがもたらすメリットを考えてみましょう。
Kotlinなどの汎用プログラミング言語でDSLを作成する場合、実際には内部DSLを意味します。 結局、独立した構文を作成するのではなく、この言語を使用する特定の方法を構成するだけです。 そして、これはまさに私たちがすでに知っているコードを使用する利点をもたらし、DSLにforループなどのさまざまな演算子を追加することを可能にします。
さらに、Kotlinは、よりクリーンな構文を作成するためのいくつかの方法を提供し、不要な文字を使いすぎないようにします。 この最初の部分では、3つの機能を検討します。
- メソッドブラケットの外側でラムダを使用する
- 引数付きラムダ(レシーバー)
- 拡張機能(翻訳者から:それらは拡張機能であり、おそらく誰もが既に聞いたことがある)
これをすべて使用する方法-いくつかの例を作成するときに、すぐに明らかになります。
すべてを可能な限り明確にするために、このパートでは、シンプルなモデルを使用してDSLを作成します。 クラスを作成するときにDSLを作成しないでください。 それは無駄です。 DSLを使用するのに適した場所は、ユーザーがモデルについて知る必要のない構成クラスまたはライブラリインターフェイスです。
では、最初のDSLを書きましょう
このパートでは、クラスPersonのオブジェクトを作成できる単純なDSLを作成します。 これは単なる例であることに注意してください。 したがって、このレッスンの最後に取得する内容の例を次に示します。
val person = person { name = "John" age = 25 address { street = "Main Street" number = 42 city = "London" } }
上記のコードはそれ自体を説明し、理解しやすいことがすぐにわかります。 開発者の経験がない人でも、これを読み、自分で変更を加えることができます。 これを再現する方法を理解するために、いくつかの手順を実行します。 以下が、開始するモデルです。
data class Person(var name: String? = null, var age: Int? = null, var address: Address? = null) data class Address(var street: String? = null, var number: Int? = null, var city: String? = null)
明らかに、これは記述できる最もクリーンなモデルではありません。 しかし、 不変のプロパティ(val)が必要です。 その前に、このシリーズの次のパートで説明します。
最初に行うことは、新しいファイルを作成することです。 その中で、モデルの実際のクラスからDSLを分離します。 Personクラスのコンストラクター関数を作成することから始めましょう。 必要な結果を見ると、 Personクラスのすべてのプロパティがコードブロックで定義されていることがわかります。 しかし実際には、中括弧はラムダを意味します。 ここでは、上記のKotlin言語の3つの機能のうち最初の機能を使用します 。 メソッドブラケットの外側でラムダを使用します 。
関数の最後のパラメーターがラムダの場合、角括弧の外側で使用できます。 また、パラメーターが1つ(ラムダ)しかない場合は、括弧を完全に削除できます。 つまり、 人{...}は 人({...})と同じです。 これにより、DSLの構文汚染が少なくなります。 次に、関数の最初のバージョンを作成します。
fun person(block: (Person) -> Unit): Person { val p = Person() block(p) return p }
だから。 ここに、Personオブジェクトを作成する関数があります。 これには、2行目に作成するオブジェクトを持つラムダが必要です。3行目にこのラムダを実行すると、4行目にオブジェクトを返す前に、オブジェクトが必要なプロパティを受け取ることが期待されます。 、作成した関数をどのように使用できますか?
val person = person { it.name = "John" it.age = 25 }
lambdaは引数を1つしか受け取らないため、それを介して personオブジェクトにアクセスできます。 かなり良いように見えますが、まだ終わっていません。 これは実際には、DSLで見たいものではありません。 特に、オブジェクトのレイヤーを追加する場合。 これにより、前述のKotlin関数: 引数付きのラムダ(レシーバー)に導かれます 。
person関数の定義では、 レシーバーをラムダに追加できます。 したがって、このラムダではこのレシーバの機能にのみアクセスできます。 関数はレシーバーのスコープ内にあるため、ラムダの引数として置き換える代わりに、レシーバーでラムダを実行するだけです。
fun person(block: Person.() -> Unit): Person { val p = Person() p.block() return p }
そして、簡単に書くことができます。 たとえば、Kotlinが提供する適用機能を使用します 。
fun person(block: Person.() -> Unit): Person = Person().apply(block)
これで、DSLから削除できます。
val person = person { name = "John" age = 25 }
よさそうですね。 :)ほぼ完了です。 しかし、1つのポイントを逃しました-Addressクラス。 目的の結果では、作成したばかりのperson関数に非常によく似ています。 ここでの唯一の違いは、Personオブジェクトのプロパティアドレスとして割り当てる必要があることです。 これを行うために、Kotlin言語の前述の3つの関数の最後であるExtension Functionsを使用します。
拡張関数を使用すると、クラス自体のソースコードにアクセスせずに、クラスに関数を追加できます。 これはAddressオブジェクトを作成するのに理想的で、 Personに 住所を入力するために直接割り当てます。 DSLファイルの最終バージョンです(今のところ)。
fun person(block: Person.() -> Unit): Person = Person().apply(block) fun Person.address(block: Address.() -> Unit) { address = Address().apply(block) }
personコンストラクター関数で行ったように、受信者としてAddressを持つラムダを受け入れるPersonにaddress関数を追加しました。 次に、作成されたAddressオブジェクトをPersonクラスのクラスに設定します。 モデルを作成するためにDSLを作成しました。
val person = person { name = "John" age = 25 address { street = "Main Street" number = 42 city = "London" } }
これは、KotlinでのDSLの記述方法に関する長いシリーズの最初の部分です。 2番目の部分では、Builderテンプレートと@DslMarkerアノテーションを使用して、コレクションを追加する方法について説明します。 GsonBuilderを使用した実際の例があります。
オリジナルの記事はこちらからご覧いただけます: KotlinでのDSLの作成(パート1)
一連のオリジナル記事の著者: FréDumazy