Kotlin DSL理論ず実践

SQL、RegExp、Gradle-それらを結合するものは䜕ですか これらはすべお、問題指向蚀語たたはDSLドメむン固有蚀語の䜿甚䟋です。 このような各蚀語は、デヌタベヌスからのデヌタのク゚リ、テキスト内の䞀臎の怜玢、アプリケヌションの構築プロセスの蚘述など、その焊点が絞られたタスクを解決したす。 Kotlin蚀語は、独自の問題指向蚀語を䜜成するための倚数の可胜性を提䟛したす。 蚘事の過皋で、プログラマヌの歊噚にどのツヌルが含たれおいるかを把握し、提案された䞻題領域にDSLを実装したす。


この蚘事で玹介する構文党䜓に぀いお、できるだけ簡単に説明したすが、この資料は、Kotlinを問題指向蚀語を構築するための蚀語ず芋なす゚ンゞニアを緎習するために蚭蚈されおいたす。 蚘事の終わりに、準備する必芁がある欠点が瀺されたす。 この蚘事で䜿甚されおいるコヌドは、Kotlinバヌゞョン1.1.4-3に関連しおおり、GitHubで入手できたす。



DSLずは䜕ですか


プログラミング蚀語は、汎甚プログラミング蚀語ずドメむン固有蚀語の2皮類に分類できたす。 DSLの䞀般的な䟋は、SQL、正芏衚珟、build.gradleです。 この蚀語は提䟛される機胜の量を枛らしたすが、同時に特定の問題を効果的に解決するこずができたす。 これは、プログラムを呜什型スタむル結果を取埗する方法ではなく、宣蚀型たたは宣蚀型に近い珟圚のタスクを蚘述するで蚘述する方法です。この堎合、問題の解決策は䞎えられた情報に基づいお埗られたす。


暙準の実行プロセスがあり、それを倉曎したり倉曎したりできる堎合がありたすが、䞀般的には異なるデヌタや結果の圢匏でそれを䜿甚したいずしたす。 DSLを䜜成するこずにより、DSLの゚ンドナヌザヌは゜リュヌションがどのように埗られるかを考えずに、同じ䞻題分野からさたざたな問題を解決するための柔軟なツヌルを䜜成したす。 これはいく぀かのAPIであり、それらを巧みに䜿甚しお、人生ず長期的なシステムサポヌトを倧幅に簡玠化できたす。


この蚘事では、Kotlin蚀語で「内郚」DSLを構築するこずを怜蚎したした。 この皮の問題指向蚀語は、汎甚蚀語の構文に基づいお実装されおいたす。 詳现に぀いおは、 こちらをご芧ください 。


応甚分野


私の意芋では、Kotlin DSLを適甚しお実蚌する最良の方法の1぀は、テストによるものです。


Javaの䞖界から来たずしたす。 かなり倧芏暡なデヌタモデルのために、暙準゚ンティティむンスタンスを䜕床も䜕床も蚘述する必芁がありたしたか このために、いく぀かのビルダヌ、たたはさらに悪いこずに、内郚のデフォルト倀を埋める特別なナヌティリティクラスを䜿甚した可胜性がありたすか オヌバヌロヌドされたメ゜ッドはいく぀ありたすか どのくらいの頻床でデフォルト倀から「やや」逞脱する必芁がありたすか。たた、今どのくらいの䜜業をしなければなりたせんか これらの質問が吊定性以倖の䜕ものでもない堎合は、正しい蚘事を読んでいたす。


同様に、教育分野に特化した私たちのプロゞェクトでは、ビルダヌずナヌティリティクラスの助けを借りお、システムの最も重芁なモゞュヌルの1぀であるカリキュラムを構築するモゞュヌルをテストでカバヌしたした。 このアプロヌチは、蚈画および怜蚌システムのさたざたなアプリケヌションの圢成のために、Kotlin蚀語ずDSLに眮き換えられたした。 以䞋に、この蚀語をどのように掻甚し、蚈画サブシステムのテストの開発を拷問から喜びに倉えた䟋を瀺したす。


この蚘事では、生埒ず教垫ずの間のクラス蚈画の小芏暡なデモンストレヌションシステムをテストするためのDSLの蚭蚈に぀いお説明したす。


䞻な機胜


Kotlinの䞻な利点をリストしたしょう。これにより、この蚀語でかなりきれいに蚘述でき、独自のDSLを構築するこずができたす。 以䞋は、䜿甚する䟡倀のある䞻芁な蚀語構文の改善点を瀺した衚です。 このリストを泚意深く確認しおください。 ほずんどのデザむンがよく知らない堎合は、順番に読むこずをお勧めしたす。 ただし、1぀たたは2぀のポむントに慣れおいない堎合は、それらに盎接アクセスできたす。 ここですべおがおなじみであれば、蚘事の最埌でDSLを䜿甚するこずの䞍利な点のレビュヌに行くこずができたす。 このリストを補足したい堎合は、コメントにオプションを蚘入しおください。


機胜名DSL構文通垞の構文
挔算子のオヌバヌラむドcollection += elementcollection.add(element)
タむプ゚むリアスtypealias Point = Pair<Int, Int>空の盞続人クラスおよびその他の束葉杖を䜜成する
get / setメ゜ッドの合意map["key"] = "value"map.put("key", "value")
耇数の宣蚀val (x, y) = Point(0, 0)val p = Point(0, 0); val x = p.first; val y = p.second
カッコ倖のラムダlist.forEach { ... }list.forEach({...})
拡匵機胜mylist.first(); // first() mylistナヌティリティ関数
挿入機胜1 to "one"1.to("one")
ハンドラヌ付きラムダPerson().apply { name = «John» }いや
コンテキスト制埡@DslMarkerいや

䜕か新しいこずを芋぀けたしたか それでは続けたしょう。


この衚では、委任されたプロパティは意図的に省略されおいたす。これは、私の意芋では、DSLが怜蚎する圢匏でDSLを構築するのに圹立たないからです。 これらの機胜のおかげで、コヌドクリヌナヌを蚘述し、倚くの「ノむズの倚い」構文を取り陀き、同時に開発をさらに楜しくするこずができたす「どこがもっずいい」 Kotlin in Actionずいう本の自然蚀語での比范が気に入りたした。たずえば、英語では、文は単語から構築され、文法芏則は単語をどのように組み合わせるかを制埡したす。 DSLでも同様に、1぀の操䜜を耇数のメ゜ッド呌び出しで構成でき、型チェックにより、構造が意味をなすようになりたす。 圓然、呌び出しの順序は必ずしも明らかではないかもしれたせんが、これはDSL蚭蚈者の良心に残りたす。


この蚘事では、「内郚DSL」、぀たり 問題指向蚀語は、ナニバヌサル蚀語-Kotlinに基づいおいたす。


最終結果の䟋


問題指向蚀語の構築を始める前に、蚘事を読んだ埌に構築できるものの結果を瀺したいず思いたす。 GitHubリポゞトリにあるすべおのコヌドは、 こちらにありたす 。 以䞋は、興味のある科目の生埒の教垫怜玢をテストするためのDSLです。 この䟋では、固定のタむムグリッドがあり、クラスが教垫ず生埒の蚈画に同時に配眮されおいるこずを確認したす。


 schedule { data { startFrom("08:00") subjects("Russian", "Literature", "Algebra", "Geometry") student { name = "Ivanov" subjectIndexes(0, 2) } student { name = "Petrov" subjectIndexes(1, 3) } teacher { subjectIndexes(0, 1) availability { monday("08:00") wednesday("09:00", "16:00") } } teacher { subjectIndexes(2, 3) availability { thursday("08:00") + sameDay("11:00") + sameDay("14:00") } } // data { } doesn't be compiled here because there is scope control with // @DataContextMarker } assertions { for ((day, lesson, student, teacher) in scheduledEvents) { val teacherSchedule: Schedule = teacher.schedule teacherSchedule[day, lesson] shouldNotEqual null teacherSchedule[day, lesson]!!.student shouldEqual student val studentSchedule = student.schedule studentSchedule[day, lesson] shouldNotEqual null studentSchedule[day, lesson]!!.teacher shouldEqual teacher } } } 

ツヌル


DSLを構築するためのツヌルの完党なリストは䞊蚘にありたした。 これらはそれぞれ䟋で䜿甚されおおり、 参照によりコヌドを調べるこずにより、そのような構造の構築を調べるこずができたす。 この䟋に䜕床も戻っお、さたざたなツヌルのデモを行いたす。 DSLを構築する決定は本質的に実蚌的であるこずに泚意するこずが重芁です。自分のプロゞェクトで芋たこずを繰り返すこずができたすが、これは提瀺されたオプションが唯䞀の真の遞択肢であるこずを意味したせん。 以䞋では、各ツヌルに぀いお詳しく調べたす。


この蚀語の䞀郚の機胜は他の機胜ずの組み合わせで特に優れおおり、このリストの最初のツヌルは括匧の倖偎のラムダです。


カッコ倖のラムダ


ドキュメント


ラムダ匏たたはラムダは、関数に枡したり、保存したり、呌び出したりできるコヌドのブロックです。 Kotlinでは、ラムダ型は次のように瀺されたす( ) -> 。 この芏則に埓うず、ラムダの最も原始的な圢匏は() -> Unitで、Unitは1぀の䟋倖を陀いおVoidの類䌌物です。 ラムダたたは関数の終わりでは
「return ...」コンストラクトを蚘述する必芁がありたす。 このため、垞に戻り倀の型がありたす。Kotlinではこれは暗黙的に行われたす。


以䞋は、ラムダを倉数に保存する最も簡単な䟋です。


 val helloPrint: (String) -> Unit = { println(it) } 

パラメヌタのないラムダの堎合、コンパむラは既知の型から独立しお型を掟生できたす。 ただし、この堎合、1぀のパラメヌタヌがありたす。 このようなラムダの呌び出しは次のずおりです。


 helloPrint("Hello") 

䞊蚘の䟋では、lambdaは1぀のパラメヌタヌを取りたす。 ラムダ内では、このパラメヌタヌはデフォルトで「it」ずいう名前になっおいたすが、耇数のパラメヌタヌがある堎合は、名前を明瀺的にリストするか、アンダヌスコア「_」を䜿甚しお無芖する必芁がありたす。 次の䟋は、この動䜜を瀺しおいたす。


 val helloPrint: (String, Int) -> Unit = { _, _ -> println("Do nothing") } helloPrint("Does not matter", 42) //output: Do nothing 

たずえば、Groovyですでに芋た基本的なツヌルは、括匧の倖偎のラムダです。 蚘事の冒頭の䟋に泚意しおください。暙準蚭蚈を陀き、ほがすべおの䞭括匧の䜿甚はラムダの䜿甚です。 x { 
 }圢匏を䜜成するには、少なくずも2぀の方法がありたす。



オプションに関係なく、ラムダを䜿甚したす。 関数x()たす。 Kotlin蚀語では、次のルヌルが適甚されたす。ラムダが関数の最埌の匕数である堎合、角括匧から倖すこずができ、ラムダが唯䞀のパラメヌタヌである堎合、角括匧を曞くこずはできたせん。 その結果、構成x({
})をx() {}に倉換できたす。その埌、括匧を削陀するず、 x {}埗られたす。 このような関数の宣蚀は次のずおりです。


 fun x( lambda: () -> Unit ) { lambda() } 

たたは単䞀行関数の省略圢で、これを曞くこずができたす


 fun x( lambda: () -> Unit ) = lambda() 

しかし、xがクラスではなく、オブゞェクトであり、関数ではない堎合はどうでしょうか 別の興味深い解決策がありたす。これは、問題指向蚀語の構築、挔算子の再定矩で䜿甚される基本抂念の1぀に基づいおいたす。 このツヌルを芋おみたしょう。


挔算子のオヌバヌラむド


ドキュメント


Kotlinは、広くはあるが限られた範囲の挔算子を提䟛したす。 挔算子修食子を䜿甚するず、特定の条件䞋で呌び出される芏則によっお関数を定矩できたす。 明らかな䟋は、2぀のオブゞェクト間で+挔算子を䜿甚するずきに実行されるplus関数です。 䞊蚘のドキュメントのリンクで、挔算子の完党なリストを芋぀けるこずができたす。


少しささいな呌び出し挔算子を考えおください。 この蚘事の䞻な䟋は、schedule {}構造から始たりたす。 蚭蚈の目的は、蚈画のテストを担圓するコヌドブロックを分離するこずです。 このような構成を構築するには、䞊蚘で怜蚎したものずは少し異なるメ゜ッドが䜿甚されたす。invoke+ "lambda out of brackets"挔算子です。 invokeオペレヌタヌを定矩するず、スケゞュヌル...構造が䜿甚可胜になりたすが、スケゞュヌルはオブゞェクトです。 実際、スケゞュヌル...呌び出しは、コンパむラによっおschedule.invoke...ずしお解釈されたす。 スケゞュヌルの宣蚀を芋おみたしょう。


 object schedule { operator fun invoke(init: SchedulingContext.() -> Unit) { SchedulingContext().init() } } 

スケゞュヌル識別子は、特別なキヌワヌドオブゞェクトでマヌクされたスケゞュヌルクラスシングルトンの唯䞀のむンスタンスに送信されるこずを理解する必芁がありたすこのようなオブゞェクトの詳现に぀いおは、 こちらを参照しおください 。 したがっお、スケゞュヌルむンスタンスでinvokeメ゜ッドを呌び出したす。この堎合、メ゜ッドぞの唯䞀のパラメヌタヌはラムダであり、これを角かっこで囲みたす。 その結果、schedule {...}コンストラクトは次ず同等です。


 schedule.invoke( {    } ) 

ただし、invokeメ゜ッドをよく芋るず、通垞のラムダではなく、「ハンドラヌ付きラムダ」たたは「コンテキスト付きラムダ」が衚瀺され、そのタむプは次のように蚘述されたす SchedulingContext.() -> Unit
それが䜕であるかを理解する時です。


ハンドラヌ付きラムダ


ドキュメント


Kotlinは、ラムダ匏のコンテキストを蚭定する機䌚を䞎えおくれたす。 コンテキストは通垞​​のオブゞェクトです。 コンテキストのタむプは、ラムダ匏のタむプずずもに決定されたす。 このようなラムダは、コンテキストクラスの非静的メ゜ッドのプロパティを取埗したすが、このクラスのパブリックAPIのみにアクセスできたす。
通垞のラムダのタむプは次のように定矩されたす () -> Unit 、タむプXのコンテキストを持぀ラムダのタむプは次のように定矩されたす X.()-> Unit 、および最初のタむプのラムダを通垞の方法で開始できる堎合


 val x : () -> Unit = {} x() 

次に、コンテキストを持぀ラムダにはコンテキストが必芁です


 class MyContext val x : MyContext.() -> Unit = {} //x() // , ..    val c = MyContext() //  cx() //  x(c) //   

スケゞュヌルオブゞェクトでinvoke挔算子を定矩したこずを思い出させおください前の段萜を参照。これにより、構造を䜿甚できたす。


 schedule { } 

䜿甚するラムダには、SchedulingContextのようなコンテキストがありたす。 デヌタメ゜ッドはこのクラスで定矩されたす。 その結果、次の構造が埗られたす。


 schedule { data { //... } } 

ご想像のずおり、デヌタメ゜ッドはコンテキストを持぀ラムダを䜿甚したすが、コンテキストは既に異なりたす。 したがっお、耇数のコンテキストが同時に䜿甚できる入れ子構造を取埗したす。


この䟋がどのように機胜するかを詳现に理解するために、すべおの構文糖を削陀したしょう。


 schedule.invoke({ this.data({ }) }) 

ご芧のずおり、すべおが非垞に簡単です。
invokeステヌトメントの実装を芋おみたしょう。


 operator fun invoke(init: SchedulingContext.() -> Unit) { SchedulingContext().init() } 

コンテキストのコンストラクタヌSchedulingContext() を呌び出し、䜜成されたオブゞェクトコンテキストで、パラメヌタヌずしお枡した識別子initでラムダを呌び出したす。 これは、通垞の関数の呌び出しに非垞に䌌おいたす。 その結果、 SchedulingContext().init() 1行で、コンテキストを䜜成し、オペレヌタヌに枡されたラムダを呌び出したす。 他の䟋に興味がある堎合は、適甚に泚意し、Kotlin暙準ラむブラリのメ゜ッドを䜿甚しおください。


最埌の䟋では、invoke挔算子ず、他のツヌルずの盞互䜜甚を調べたした。 次に、正匏には挔算子であり、コヌドを簡朔にする別のツヌル、぀たりget / setメ゜ッドの芏則に焊点を圓おたす。


get / setメ゜ッドの合意


ドキュメント


DSLを開発するずき、1぀以䞊のキヌを䜿甚しお連想配列にアクセスするための構文を実装できたす。 以䞋の䟋を芋おください。


 availabilityTable[DayOfWeek.MONDAY, 0] = true println(availabilityTable[DayOfWeek.MONDAY, 0]) //output: true 

角括匧を䜿甚するには、挔算子修食子を䜿甚しお、必芁なもの読み取りたたは曞き蟌みに応じおgetたたはsetメ゜ッドを実装する必芁がありたす。 このツヌルの実装䟋は、 リンクの GitHubのMatrixクラスにありたす。 これは、マトリックスを操䜜するためのラッパヌの最も単玔な実装です。 以䞋は、興味のあるコヌドの䞀郚です。


 class Matrix(...) { private val content: List<MutableList<T>> operator fun get(i: Int, j: Int) = content[i][j] operator fun set(i: Int, j: Int, value: T) { content[i][j] = value } } 

getおよびset関数のパラメヌタヌタむプは、想像力によっおのみ制限されたす。 get / set関数に1぀たたは耇数のパラメヌタヌを䜿甚しお、デヌタにアクセスするための快適な構文を提䟛できたす。 Kotlinのオペレヌタヌは、ドキュメントに蚘茉されおいる倚くの興味深い機胜を提䟛したす。


驚いたこずに、Kotlin暙準ラむブラリにはPairクラスがありたすが、なぜですか コミュニティの倧郚分は、Pairクラスが悪いず考えおいたす。これにより、2぀のオブゞェクトの接続の意味がなくなり、それらがペアになっおいる理由が明らかになりたせん。 次の2぀のツヌルは、カップルの意味を保存し、䜙分なクラスを䜜成しない方法を瀺しおいたす。


タむプ゚むリアス


ドキュメント


敎数座暙を持぀平面䞊の点にラッパヌクラスが必芁だず想像しおください。 原則ずしお、 Pair<Int, Int>クラスは私たちに適しおいたすが、このタむプの倉数では、倀をペアでバむンドする理由の理解が䞀時的に倱われる堎合がありたす。 明らかな修正は、独自のクラスを䜜成するか、さらに悪いこずです。 Kotlinでは、開発者の兵噚庫には、次のように蚘述されたタむプ゚むリアスが補充されたす。


 typealias Point = Pair<Int,Int> 

実際、これはコンストラクトの通垞の名前倉曎です。 このアプロヌチのおかげで、Pointクラスを䜜成する必芁はありたせん。この堎合、単玔にカップルを耇補したす。 これで、次のようにポむントを䜜成できたす。


 val point = Point(0, 0) 

ただし、Pairクラスには2぀のプロパティがありたす。1぀目ず2぀目です。これらのプロパティの名前を倉曎しお、目的のPointクラスずPairクラスの違いを消去するにはどうすればよいでしょうか。 プロパティ自䜓の名前を倉曎するこずはできたせんが、ツヌルキットには、職人が耇数の宣蚀ずしお指定した玠晎らしい機䌚がありたす。


耇数宣蚀砎壊宣蚀


ドキュメント


䟋の理解を簡単にするために、状況を考えおみたしょう。䞊蚘の䟋からわかるように、Point型のオブゞェクトがあるため、これは名前が倉曎されたPair<Int, Int>型にすぎたせん。 暙準ラむブラリのPairクラスの実装からわかるように、デヌタ修食子でマヌクされおいたす。これは、ずりわけ、このクラスで生成されたcomponentNメ゜ッドを取埗するこずを意味したす。 それらに぀いお話したしょう。


どのクラスに察しおも、コンポヌネントの1぀のプロパティぞのアクセスを提䟛するcomponentN挔算子を定矩できたす。 これは、point.component1の呌び出しがpoint.firstの呌び出しず同等であるこずを意味したす。 次に、この耇補が必芁な理由を芋おみたしょう。


耇数宣蚀ずは䜕ですか これは、オブゞェクトを倉数に「分解」する方法です。 この機胜のおかげで、次の構成を蚘述できたす。


 val (x, y) = Point(0, 0) 

䞀床に耇数の倉数を宣蚀する機䌚がありたすが、倀ずしおはどうなりたすか このため、シリアル番号に応じおcomponentNの生成されたメ゜ッドが必芁になりたす。Nの代わりに、1から開始しお、オブゞェクトをそのプロパティのセットに分解できたす。 したがっお、たずえば、䞊蚘の゚ントリは次ず同等です。


 val pair = Point(0, 0) val x = pair.component1() val y = pair.component2() 

これは次ず同等です


 val pair = Point(0, 0) val x = pair.first val y = pair.second 

ここで、firstずsecondはPointオブゞェクトのプロパティです。


Kotlinのforコンストラクトの圢匏は次のずおりです。xは倀1、2、および3を順番に受け取りたす。


 for(x in listOf(1, 2, 3)) { 
 } 

䞻な䟋のDSLのassertionsブロックに泚意しおください。 䟿宜䞊、その䞀郚を以䞋に瀺したす。


 for ((day, lesson, student, teacher) in scheduledEvents) { 
 } 

これですべおが明らかになりたす。 scheduleEventsコレクションを反埩凊理したす。各芁玠は、珟圚のオブゞェクトを蚘述する4぀のプロパティに分解されたす。


拡匵機胜


ドキュメント


サヌドパヌティラむブラリのオブゞェクトに独自のメ゜ッドを远加したり、Java Collection Frameworkにメ゜ッドを远加したりするこずは、倚くの開発者にずっお長幎の倢です。 そしお今、私たち党員にそのような機䌚がありたす。 拡匵関数の宣蚀は次のずおりです。


 fun AvailabilityTable.monday(from: String, to: String? = null) 

通垞のメ゜ッドずは異なり、メ゜ッド名の前にクラス名を远加しお、どのクラスを展開するかを瀺したす。 この䟋では、 AvailabilityTableはMatrixタむプの゚むリアスであり、Kotlinの゚むリアスは名前の倉曎にすぎないため、そのような宣蚀は以䞋の䟋の宣蚀ず同等であり、必ずしも䟿利ではありたせん。

 fun Matrix<Boolean>.monday(from: String, to: String? = null) 

しかし、残念ながら、ツヌルを䜿甚しないか、特定のコンテキストクラスにのみメ゜ッドを远加するこずを陀いお、䜕もできたせん。 そしお、魔法は必芁な堎所にのみ珟れたす。 さらに、これらの機胜を䜿甚しおむンタヌフェヌスを拡匵するこずもできたす。 良い䟋は、次のようにIterableオブゞェクトを拡匵する最初のメ゜ッドです。


 fun <T> Iterable<T>.first(): T 

その結果、芁玠のタむプに関係なく、Iterableむンタヌフェむスに基づくコレクションは最初のメ゜ッドを取埗したす。 興味深いのは、拡匵メ゜ッドをコンテキストクラスに配眮できるため、特定のコンテキストでのみ拡匵メ゜ッドにアクセスできるこずです䞊蚘のコンテキストのラムダを参照。 さらに、Nullable型の拡匵関数も䜜成できたすNullable型の説明は蚘事の範囲倖ですが、必芁に応じおここで読むこずができたす。 たずえば、CharSequenceタむプを拡匵するKotlin暙準ラむブラリのisNullOrEmpty関数は、次のように䜿甚できたす。


 val s: String? = null s.isNullOrEmpty() //true 

この関数のシグネチャは次のずおりです。


 fun CharSequence?.isNullOrEmpty(): Boolean 

このようなKotlin関数を䜿甚しおJavaから䜜業する堎合、拡匵関数は静的ずしお䜿甚できたす。


挿入機胜


ドキュメント


構文を甘くする別の方法は、䞭眮関数を䜿甚するこずです。 簡単に蚀えば、このツヌルのおかげで、単玔な状況で䞍必芁なコヌドノむズを取り陀くこずができたした。
メむンサンプル蚘事のassertionsブロックは、このツヌルの䜿甚方法を瀺しおいたす。


 teacherSchedule[day, lesson] shouldNotEqual null 

この蚭蚈は次ず同等です。


 teacherSchedule[day, lesson].shouldNotEqual(null) 

ブラケットずドットが冗長な堎合がありたす。 この堎合、関数に䞭眮修食子が必芁です。
䞊蚘のコヌドでは、 teacherSchedule[day, lesson]コンストラクトはスケゞュヌルアむテムを返し、 shouldNotEqual関数はアむテムがnullでないこずを確認したす。


そのような関数を宣蚀するには、以䞋を行う必芁がありたす。



以䞋のコヌドのように、最埌の2぀のツヌルを組み合わせるこずができたす。


 infix fun <T : Any?> T.shouldNotEqual(expected: T) 

デフォルトのゞェネリック型はNullable型階局ではなくAny子孫ですが、そのような堎合はnullを䜿甚できないため、Any型を明瀺的に指定する必芁がありたす。


コンテキスト制埡


ドキュメント


倚くのネストされたコンテキストを䜿甚するず、非垞に䜎いレベルで爆発的な混合物が発生するため、たずえば制埡なしで、次の構成が意味をなさない堎合がありたす。


 schedule { // SchedulingContext data { // DataContext +   SchedulingContext data { } // -    } } 

Kotlin 1.1より前には、これを回避する方法がすでにありたした。 DataContextのネストされたコンテキストで独自のデヌタメ゜ッドを䜜成し、ERRORレベルのDeprecatedアノテヌションでマヌクしたす。


 class DataContext { @Deprecated(level = DeprecationLevel.ERROR, message = "Incorrect context") fun data(init: DataContext.() -> Unit) {} } 

このアプロヌチのおかげで、無効なDSLビルドの可胜性を排陀できたした。 ただし、SchedulingContextの倚数のメ゜ッドでは、䞀定量のルヌチン䜜業が行われたため、コンテキストを制埡するすべおの欲求が抑えられたした。


Kotlin 1.1は、新しい制埡ツヌル-@DslMarkerアノテヌションを導入したす。 これは、独自の泚釈に適甚されたす。泚釈は、コンテキストをマヌクするために必芁です。 泚釈を䜜成したしょう。泚釈は、歊噚庫にある新しいツヌルを䜿甚しおマヌクしたす。


 @DslMarker annotation class MyCustomDslMarker 

次に、コンテキストにラベルを付ける必芁がありたす。 䞻な䟋では、これらはSchedulingContextずDataContextです。 各クラスを単䞀のDSLマヌカヌでマヌクするずいう事実により、次のこずが起こりたす。


 @MyCustomDslMarker class SchedulingContext { ... } @MyCustomDslMarker class DataContext { ... } fun demo() { schedule { // SchedulingContext data { // DataContext +     SchedulingContext // data { } // , ..    DSL  } } } 

倚くの時間ず劎力を削枛するこのようなアプロヌチのすべおの喜びにもかかわらず、1぀の問題が残っおいたす。 メむンの䟋に泚目するず、次のコヌドが衚瀺されたす。


 schedule { data { student { name = "Petrov" } ... } } 

Student, , , , , @MyCustomDslMarker , , , .


Student data {} , .. DataContext , :


 schedule { data { student { student { } } } } 

, , . :


  1. , , StudentContext. @DslMarker.
  2. , , IStudent ( ), -, , , .
     @MyCustomDslMarker class StudentContext(val owner: Student = Student()): IStudent by owner 
  3. @Deprecated, . , , , .
    deprecated extension Identifiable .

 @Deprecated("Incorrect context", level = DeprecationLevel.ERROR) fun Identifiable.student(init: () -> Unit) {} 

, , DSL .


DSL


DSL Kotlin , DSL .


DSL


, DSL, . DSL extension , .


, , : " callback'", DSL, . , , . , DSL , .


This, it!?


this it DSL. - it, , , , . , .


, . " " DSL. , , , val mainContext = this



. . , , , " ", . , DSL, , , DSL , - . DSL (, ), , .. .


, ?


- DSL, : " ?". . DSL, , . , . , .. - : " , ?" , , .


おわりに


, - . , .


, - , , . , DSL . , , .


" ", , DSL , , , , .
- !



Source: https://habr.com/ru/post/J341402/


All Articles