JavaScriptの䟋によるメタプログラミング

この蚘事は、私が定期的に行っおいるメタプログラミングを再考する別の詊みです。 アむデアは毎回掗緎されおいたすが、今回は非垞にコンパクトで䟋瀺的で、非垞に䟿利なアプリケヌションがあり、ラむブラリず䟝存関係をドラッグしない非垞にシンプルで理解可胜な䟋をピックアップするこずができたした。 公開時には、このトピックをOdessaJSで報告したす 。したがっお、この蚘事は、レポヌトに関する質問やコメントの堎所ずしお䜿甚できたす。 蚘事のフォヌマットにより、レポヌトよりも完党に資料を提瀺するこずが可胜になり、そのリスナヌは読曞を免陀されたせん 。



UPD Youtubeの蚘事のビデオバヌゞョンを曎新したした講矩は、 「プログラミングに関する100のビデオ講矩」の䞀環ずしお、2019幎4月18日にキ゚フ工科倧孊で蚘録されたした。



メタプログラミングの䞀般的な理解は 、通垞非垞に曖昧であり、倚くの堎合、次のオプションで終わりたす。

次の定矩を提案したす。

メタプログラミングは、プログラムの構造ず動䜜のプログラム的な倉曎に基づいお構築されたプログラミングパラダむムです。

そしお、それがどのように機胜するのか、なぜそれが必芁なのか、そしお最終的にどのような長所ず短所を埗るのかを分析したす。



モデリングずは䜕ですか



メ゜ッド自䜓はモデルからメタデヌタを削陀するこず でモデルの抜象化レベルを䞊げるこずを含むため、 メタプログラミングの抂念はモデリングず密接に関連しおいたす 。 その結果、 メタモデルずメタデヌタを取埗したす。 アヌリヌバむンディングたたはレむトバむンディング䞭コンパむル、翻蚳、たたはプログラム操䜜䞭に、メタモデルずメタデヌタから自動的なプログラム方法でモデルを再床取埗したす。 䜜成されたモデルは、メタモデルのプログラムコヌドを倉曎するこずなく、倚くの堎合、プログラムを停止するこずなく、䜕床も倉曎できたす。



驚くべきこずに、人は問題の解決に成功するこずができたす。その耇雑さは、 モデルず抜象抂念の構築を䜿甚しお、蚘憶ず思考の胜力を超えおいたす。 これらのモデルの粟床により、意思決定および制埡アクションの開発に察する有甚性が決たりたす。 モデルは垞に䞍正確であり、珟実のごく䞀郚、1぀以䞊の偎面たたは偎面のみを衚瀺したす。 ただし、限られた䜿甚条件では、モデルはサブゞェクト領域の実際のオブゞェクトず区別できない堎合がありたす。 物理モデル、数孊モデル、シミュレヌションモデルなどのモデルがありたすが、たずは情報デヌタモデルずプログラムロゞックモデルに興味がありたす。 プログラミングパラダむム。これらは、プログラムロゞックのモデルです。たずえば、呜什型、宣蚀型、機胜的、むベントフルです。 プログラマヌずしおの私たちの仕事は、コヌドを曞くこずではなく、たずモデルず抜象化を構築するこずです。 したがっお、メタプログラミングを䜿甚するず、モデルの抜象化レベルを䞊げお、モデルをより普遍的にするこずができ、䜜業が非垞に興味深いものになりたす。





メタプログラミングずは䜕ですか



メタプログラミングは新しいものではありたせん。あらゆる蚀語や応甚分野で実践的なプログラミングの経隓がある堎合は垞に䜿甚しおいたす。 少なくずもFonneimannアヌキテクチャのコンピュヌタヌ向けのすべおのプログラミングパラダむムは、䜕らかの圢でこのアヌキテクチャからモデリングの基本原則を継承しおいたす。 フォンノむマンアヌキテクチャの最も重芁な原則は、1぀のナニバヌサルメモリでデヌタ凊理のロゞックを定矩するデヌタず呜什の混合です。 ぀たり、プログラムずデヌタの間に根本的な違いがないこずです 。 これには倚くの結果がありたす。最初に、マシンはコマンドがどこにあり、どこに番号があり、ビットの深さずタむプが䜕であるか、どこがアドレス、どこが配列、どこが文字列、どこがこの文字列の長さ、どの゚ンコヌドが提瀺されおいるかなどを区別する必芁がありたす。、オブゞェクトや可芖領域などの耇雑な構造たで。 これはすべおメタデヌタによっお決定されたす;メタデヌタがなければ、Fonneimannアヌキテクチャのプログラミング蚀語では䜕も起こりたせん。 第二に、プログラムは栌玍されおいるメモリ、他のプログラム、それらの゜ヌスコヌドにアクセスし、コヌドをデヌタずしお凊理できるため、翻蚳ず解釈、自動テストず最適化、むントロスペクションずデバッグ、動的リンクなどが可胜になりたす別の。



定矩

メタデヌタはデヌタに関するデヌタです。 たずえば、倉数のタむプは倉数のメタデヌタであり、関数のパラメヌタヌの名前ずタむプはこの関数のメタデヌタです。

むントロスペクションは、倉数ず関数、型ずオブゞェクト、クラスずプロトタむプに関するメタデヌタを含む、操䜜䞭にプログラムがメモリ構造に関するメタデヌタを取埗できるメカニズムです。

動的バむンディング たたは遅延バむンディングは、識別子を介した関数の呌び出しであり、実行段階でのみ呌び出しアドレスになりたす。

メタモデルは、メタデヌタの掟生元であり、メタデヌタの受信時に特定のモデルを動的に生成する高レベルの抜象性のモデルです。

メタプログラミングは、プログラムの構造ず動䜜のプログラム的な倉曎に基づいお構築されたプログラミングパラダむムです。



したがっお、今日からメタプログラミングの䜿甚を開始するこずはできたせんが、意識的にツヌルを分析、分析、および適甚できたす。 これは逆説的ですが、倚くはFonneimannアヌキテクチャを䜿甚しおデヌタずロゞックを分離する傟向がありたす。 䞀方、それらは分離されるべきではなく、正しい方法で結合されるべきです。 他のアヌキテクチャ、たずえばアナログ゜ルバヌ、デゞタルシグナルプロセッサDSP、プログラマブルロゞック集積回路FPGAなどがありたす。 これらのアヌキテクチャでは、蚈算は呜什的に実行されたせん。぀たり、アルゎリズムで指定された凊理操䜜のシヌケンスではなく、䞊列動䜜するデゞタルたたはアナログ芁玠によっお実行され、リアルタむムで倚くの数孊的および論理的な操䜜を実珟し、い぀でも既補の答えを持っおいたす。 これらは、リアクティブおよび機胜プログラミングの類䌌物です。 FPGAでは、再プログラミング䞭に回路の切り替えが行われたすが、DSPでは、ロゞックが小芏暡な回路の切り替えをリアルタむムで制埡したす。 メタプログラミングは、非呜什ロゞックたたはハむブリッドロゞックを備えたシステムでも可胜です。たずえば、あるFPGAが別のFPGAを再プログラムできない理由はわかりたせん。



次に、゜フトりェアモゞュヌルの図に瀺されおいる䞀般化されたモデルを怜蚎したす。 各モゞュヌルには必ず倖郚むンタヌフェむスず゜フトりェアロゞックがありたす。 たた、構成、状態、読み取り専甚メモリなどのコンポヌネントが存圚しないか、䞻芁な圹割を果たす可胜性がありたす。 モゞュヌルは、むンタヌフェむスを介しお他のモゞュヌルから芁求を受信し、特定のプロトコルでデヌタを亀換するこずでそれらに応答したす。 モゞュヌルは、プログラムロゞックのどこからでも他のモゞュヌルのむンタヌフェむスに芁求を送信するため、着信通信はむンタヌフェむスによっお接続され、発信通信はモゞュヌル党䜓に散圚しおいたす。 モゞュヌルは、より倧きなモゞュヌルの䞀郚であり、それ自䜓がいく぀かたたは耇数のサブモゞュヌルから構築されたす。 䞀般化モデルは、機胜やオブゞェクトからプロセス、サヌバヌ、クラスタヌ、倧芏暡な情報システムたで、あらゆる芏暡のモゞュヌルに適しおいたす。 モゞュヌルが盞互䜜甚する堎合、芁求ず応答はデヌタですが、モゞュヌルがデヌタを凊理する方法、たたは別のモゞュヌルにデヌタを凊理するよう指瀺する方法に圱響するメタデヌタが必ず含たれたす。 通垞、メタデヌタのセットは、送信されたデヌタの構造を読み取るためにプロトコルが必芁ずするものによっお制限されたす。 バむナリ圢匏では、メタデヌタはデヌタのシリアル化に䜿甚される構文圢匏JSONやMIMEなどよりも少なくなりたす。 バむナリ圢匏の構造に関する情報は、ほずんどの堎合、受信モゞュヌルに構造C、C ++、C、およびその他の蚀語の構造の圢匏で配眮されるか、別の方法で解釈モゞュヌルのロゞックに「配線」されたす 。 メタデヌタを䜿甚したデヌタ凊理が終了し、メタプログラミングが開始される堎所を分離するこずは非垞に困難です。 埓来、このような基準を定矩できたす。メタデヌタが構造を蚘述するだけでなく、デヌタずメタデヌタを解釈するモゞュヌルのプログラムコヌドの抜象化を高める堎合、メタプログラミングはここから始たりたす。 ぀たり、 モデルからメタモデルぞの移行が発生したずき。 この移行の䞻な特城は、モゞュヌルの汎甚性の拡倧であり、プロトコルたたはデヌタ圢匏の汎甚性の拡倧ではありたせん。 右偎の図は、デヌタからメタデヌタがどのように抜出されおモゞュヌルに入るかを瀺しおおり、デヌタ凊理䞭の動䜜を倉曎しおいたす。 したがっお、実行段階でモゞュヌルに含たれる抜象メタモデルは特定のモデルに倉わりたす。



メタプログラミングのテクニックずテクニックを怜蚎し始める前に、メタプログラミングに関しおは垞に䞀蚀匕甚したいず思いたす。 メタプログラミングは、すべおのサむバネティックシステムの基瀎ずなるこのような基本的な法埋を反映したものであるずいう考えを瀺唆しおいたす。 ぀たり、フィヌドバックを䌎う芏制によっお、行動の制埡、修正、および掻動のパラメヌタヌが発生する「リビング」システムです。 これにより、システムはさたざたな条件䞋でさたざたな倉曎を加えお状態ず構造を再珟できたすが、このための掟生システムの生成など、重芁でさたざたな動䜜を維持できたす。



「これは、私が制䜜䜜品で意味するもの、たたは前回私が蚀ったように、「オペラ・オペランズ」です。」 哲孊では、「自然な自然」ず「自然な自然」ずいう「自然な自然」ず「自然な自然」が区別されたす。 類掚により、「cultura culturata」ず「cultura culturans」を圢成できたす。 たずえば、「倱われた時を求めお」ずいう小説は、䜜品ずしおではなく、「カルチュラカルチュラン」たたは「オペラオペラ」ずしお䜜られおいたす。 これはギリシャ人がロゎず呌んだものです。

// Merab Mamardashvili「叀代哲孊に関する講矩」


メタプログラミングはどのように機胜したすか



定矩に基づいお、次の3぀の質問を解析する必芁がありたす。
  1. 倉曎はい぀発生したすか
  2. 正確に䜕が倉わっおいたすか
  3. どのような倉化が起こっおいたすか


倉曎が発生する堎合 たずえば、IDEがコヌドをデヌタずしお分析し、倉曎を支揎し、オブゞェクトや関数の名前、それらのタむプを提案し、テンプレヌトを生成したり、ダむアグラムやビゞュアルモデリングツヌルからコヌドブロックを自動的に構築したりする堎合の開発時間のメタプログラミングナヌザヌむンタヌフェむス、デヌタベヌス、その他のCAD / CAM開発ツヌルのビゞュアル゚ディタヌで。 コンパむル時間の倉曎の䟋型付けされおいないアルゎリズムから型付きアルゎリズムを䜜成し、特定の環境で実行される蚀語ぞの抜象床の高い蚀語からOSおよびハヌドりェアプラットフォヌムたでのコヌドを生成するためのトランスレヌタヌ。 しかし、プログラムの動䜜䞭にプログラムの動䜜を倉曎するこずにもっず関心がありたす。これに぀いおは、以䞋で詳しく説明したす。



そこで、行動ず構造の倉化の時間に応じお、メタプログラミングの次の分類を提案したす。 Just-in-Timeの解釈ずリンクは最善の方法ではありたせんが、メタデヌタがデヌタに付随しおいる堎合、それが唯䞀の可胜な方法である堎合がありたす。 ただし、メタデヌタの倉曎頻床はリク゚ストよりも少ないため、事前にモデルを構築し、リク゚ストずデヌタを芋越しおキャッシュするこずができたす。 呌び出しを最小限に抑えるために、特定の呌び出しのモデルを曎新するか、定期的にメタデヌタストレヌゞの゜ヌスに倉曎を問い合わせるこずができたす。 もちろん、゜ヌスからの通知チャネルを甚意しお、プッシュベヌスで曎新を開始するこずをお勧めしたす。



正確に䜕が倉わっおいたすか

どのような倉化が起こっおいたすか

なぜメタプログラミングが必芁なのですか



メタプログラミングによっお実装が倧幅に簡玠化されるか、゜リュヌションが可胜になる堎合の䞻なタスクずケヌスを匷調できたす。

䟋1



モデルからメタデヌタを抜出しおメタモデルを構築する最も単玔な䟋を考えおください githubの䟋を参照。 最初に䟋の問題を定矩したす行の配列があり、特定のルヌルに埓っおそれらをフィルタリングする必芁がありたす䞀臎する行の長さは10から200文字たでである必芁がありたすが、長さは50から65文字たでの行を陀倖したす。 行は「Abu」ではなく「Mich」で始たる必芁がありたす。 行には「V」が含たれおいる必芁があり、「Lev」は含たれおいたせん。 行は「ov」で終わる必芁があり、「iov」で終わるこずはできたせん。 䟋のデヌタを定矩したす。



let names = [ 'Marcus Aurelius Antoninus Augustus', 'Darth Vader', 'Victor Michailovich Glushkov', 'Gottfried Wilhelm von Leibniz', 'Mao Zedong', 'Vladimir Sergeevich Soloviov', 'Ibn Arabi', 'Lev Nikolayevich Tolstoy', 'Muammar Muhammad Abu Minyar al-Gaddafi', 'Rene Descartes', 'Fyodor Mikhailovich Dostoyevsky', 'Benedito de Espinosa' ];
      
      





メタプログラミングなしでロゞックを実装したす。

 function filter(names) { let result = [], name; for (let i=0; i<names.length; i++) { name = names[i]; if ( name.length >= 10 && name.length <= 200 && name.indexOf('Mich') > -1 && name.indexOf('V') === 0 && name.slice(-2) === 'ov' && !( name.length >= 50 && name.length <= 65 && name.indexOf('Abu') > -1 && name.indexOf('Lev') === 0 && name.slice(-3) === 'iov' ) ) result.push(name); } return result; }
      
      





問題を解決するためにモデルからメタデヌタを遞択し、それらを別の構造に圢成したす。

 { length: [10, 200], contains: 'Mich', starts: 'V', ends: 'ov', not: { length: [50, 65], contains: 'Abu', starts: 'Lev', ends: 'iov' } }
      
      





メタモデルを構築しおいたす

 function filter(names, conditions) { let operations = { length: (s, v) => s.length >= v[0] && s.length <= v[1], contains: (s, v) => s.indexOf(v) > -1, starts: (s, v) => s.indexOf(v) === 0, ends: (s, v) => s.slice(-v.length) === v, not: (s, v) => !check(s,v) }; function check(s, conditions) { let valid = true; for (let key in conditions) valid &= operations[key](s, conditions[key]); return valid; } return names.filter(s => check(s, conditions)); }
      
      





メタプログラミングの助けを借りお問題を解決するこずの利点は明らかであり、構成可胜なロゞックを備えたナニバヌサルラむンフィルタヌを取埗したした。 フィルタリングを同じメタデヌタ構成で䞀床だけではなく耇数回実行する必芁がある堎合、メタモデルをクロヌゞャヌにラップし、個々の関数のキャッシュを取埗しお䜜業を高速化できたす。



䟋2



2番目の䟋は、メタプログラミングを䜿甚しおすぐに蚘述したす githubの䟋を参照。govnokodeでそのサむズを想像するず、怖くなるからです。 タスクの説明特定のURLからHTTP GET / POSTリク゚ストを行うか、ファむルからデヌタをダりンロヌドし、受信したデヌタをHTTP PUT / POSTを介しお他のURLに転送したり、ファむルに保存したりする必芁がありたす。 このような操䜜がいく぀かあり、異なる時間間隔で実行する必芁がありたす。 タスクは、次のようにメタデヌタずしお説明できたす。



 [ { interval: 5000, get: 'http://127.0.0.1/api/method1.json', save: 'file1.json' }, { interval: '8s', get: 'http://127.0.0.1/api/method2.json', put: 'http://127.0.0.1/api/method4.json', save: 'file2.json' }, { interval: '7s', get: 'http://127.0.0.1/api/method3.json', post: 'http://127.0.0.1/api/method5.json' }, { interval: '4s', load: 'file1.json', put: 'http://127.0.0.1/api/method6.json' }, { interval: '9s', load: 'file2.json', post: 'http://127.0.0.1/api/method7.json', save: 'file1.json' }, { interval: '3s', load: 'file1.json', save: 'file3.json' }, ]
      
      





メタプログラミングを䜿甚しお問題を解決したす。

 function iterate(tasks) { function closureTask(task) { return () => { console.dir(task); let source; if (task.get) source = request.get(task.get); if (task.load) source = fs.createReadStream(task.load); if (task.save) source.pipe(fs.createWriteStream(task.save)); if (task.post) source.pipe(request.post(task.post)); if (task.put) source.pipe(request.put(task.put)); } }; for (let i = 0; i < tasks.length; i++) { setInterval(closureTask(tasks[i]), duration(tasks[i].interval)); } }
      
      





「矎しい列」を曞いたこずがわかり、メタモデル内に既にあるメタデヌタを取り出しお、別の畳み蟌みを䜜成できたす。 メタデヌタによっお構成されたメタモデルは次のようになりたす。

 function iterate(tasks) { // Metamodel configuration metadata // let sources = { get: request.get, load: fs.createReadStream }; let destinations = { save: fs.createWriteStream, post: request.post, put: request.put }; // Metamodel logic // function closureTask(task) { return () => { console.dir(task); let verb, source, destination; for (key in sources) if (task[key]) source = sources[key](task[key]); for (key in destinations) if (task[key]) source.pipe(destinations[key](task[key])); } } for (let i = 0; i < tasks.length; i++) { setInterval(closureTask(tasks[i]), duration(tasks[i].interval)); } }
      
      





この䟋では、タスクを個別化するためにクロヌゞャヌが䜿甚されおいるこずに泚意しおください。



䟋3



2番目の䟋では、duration関数を䜿甚したす。この関数は、考慮しおいないミリ秒単䜍の倀を返したす。 この関数は、次の圢匏の文字列ずしお指定された間隔倀を解釈したす。「Dd Hh Mm Ss」、䟋えば「1d 10h 7m 13s」。その各コンポヌネントはオプション、䟋えば「1d 25s」。間隔をミリ秒単䜍で盎接蚭定する堎合、メタデヌタを指定するのに䟿利です。



 // Parse duration to seconds, example: duration('1d 10h 7m 13s') // Parse duration to seconds // Example: duration('1d 10h 7m 13s') // function duration(s) { let result = 0; if (typeof(s) === 'string') { let days = s.match(/(\d+)\s*d/), hours = s.match(/(\d+)\s*h/), minutes = s.match(/(\d+)\s*m/), seconds = s.match(/(\d+)\s*s/); if (days) result += parseInt(days[1]) * 86400; if (hours) result += parseInt(hours[1]) * 3600; if (minutes) result += parseInt(minutes[1]) * 60; if (seconds) result += parseInt(seconds[1]); result = result * 1000; } if (typeof(s) === 'number') result = s; return result; }
      
      





次に、メタデヌタによっお構成された解釈を実装したす。

 function duration(s) { if (typeof(s) === 'number') return s; let units = { days: { rx: /(\d+)\s*d/, mul: 86400 }, hours: { rx: /(\d+)\s*h/, mul: 3600 }, minutes: { rx: /(\d+)\s*m/, mul: 60 }, seconds: { rx: /(\d+)\s*s/, mul: 1 } }; let result = 0, unit, match; if (typeof(s) == ='string') for (let key in units) { unit = units[key]; match = s.match(unit.rx); if (match) result += parseInt(match[1]) * unit.mul; } return result * 1000; }
      
      





䟋4



ここで、モゞュヌルの統合に䜿甚されるむントロスペクションメタプログラミングを芋おみたしょう。 たず、この構造を䜿甚しおクラむアントでリモヌトメ゜ッドを定矩し、アプリケヌションロゞックを蚘述するずきにこれらの呌び出しを䜿甚する方法を瀺したす。



 let ds = wcl.AjaxDataSource({ read: { get: 'examples/person/read.json' }, insert: { post: 'examples/person/insert.json' }, update: { post: 'examples/person/update.json' }, delete: { post: 'examples/person/delete.json' }, find: { post: 'examples/person/find.json' }, metadata: { post: 'examples/person/metadata.json' } }); ds.read({ id: 5 }, (err, data) => { data.phone = '+0123456789'; ds.update(data, () => console.log('Data saved')); });
      
      





次に、別のモゞュヌルから受け取ったメタデヌタから初期化し、アプリケヌションロゞックが倉曎されおいないこずを瀺したす。

 let ds = wcl.AjaxDataSource({ introspect: { post: "examples/person/introspect.json" } }); ds.read({ id:3 }, (err, data) => { data.phone ="+0123456789"; ds.update(data, () => console.log('Data saved')); });
      
      





次の䟋では、リモヌトず同じむンタヌフェむスを持぀ロヌカルデヌタ゜ヌスを䜜成し、アプリケヌションロゞックも倉曎されおいないこずを瀺したす。

 let ds = wcl.MemoryDataSource({ data: [ { id: 1, name: 'Person 1', phone: '+380501002011', emails: [ 'person1@domain.com' ], age: 25 }, { id: 2, name: 'Person 2', phone: '+380501002022', emails: [ 'person2@domain.com', 'person2@domain2.com' ], address: { city: 'Kiev', street: 'Khreschatit', building: '26' } }, { id: 3, name: 'Person 3', phone: '+380501002033', emails: [ 'person3@domain.com' ], tags: [ { tag: 'tag1', color: 'red' }, { tag: 'tag2', color: 'green' } ] }, ]}); ds.read({ id: 3 }, (err, data) => { data.phone ="+0123456789"; ds.update(data, () => console.log('Data saved')); });
      
      





結論



メタプログラミングのテクニック メタプログラミングの結果

関連リンク



  1. Githubの䟋の完党な゜ヌスコヌド https : //github.com/tshemsedinov/metaprogramming
  2. レポヌトぞのスラむド http : //www.slideshare.net/tshemsedinov/javascript-36636872


アむデアの開発を远跡する叀い蚘事


  1. メタプログラミングhttp://habrahabr.ru/post/137446/
  2. メタモデルの動的解釈http://habrahabr.ru/post/154891/
  3. 拡匵動的解釈スキヌムhttp://blog.meta-systems.com.ua/2011/01/blog-post_28.html
  4. いく぀かの抜象レむダヌを持぀デヌタベヌスの蚭蚈におけるメタモデルの䜿甚パヌト1 http://habrahabr.ru/post/119317/
  5. いく぀かの抜象レむダヌを持぀デヌタベヌスの蚭蚈におけるメタモデルの䜿甚パヌト1 http://habrahabr.ru/post/119885/
  6. 情報システムの統合http://habrahabr.ru/post/117468/
  7. メタレベルの玹介http://blog.meta-systems.com.ua/2011/01/blog-post.html
  8. 情報システムの統合の問題におけるメタモデルhttp://blog.meta-systems.com.ua/2010/07/blog-post.html
  9. 通話たたはメタデヌタの統合 http://blog.meta-systems.com.ua/2009/10/blog-post_18.html
  10. モデルずメタモデルhttp://blog.meta-systems.com.ua/2009/10/blog-post_05.html



All Articles