D暙準ラむブラリからの関数の解析

こんにちはHabr、D。蚀語の短いツアヌに皆さんを招埅したいず思いたす。 たあ、なぜ人々は遠足に行くのですか-楜しんで、䜕か新しいものを芋お、䞀般的に-それは面癜いです。 Dは新しいものや少なくずも若いものず呌ぶこずはほずんどできたせんが、過去数幎間で急速な開発が行われ、 Andrei Alexandrescuはコミュニティに参入し、すぐに䞻芁な開発者ずなりたした。



圓初から、Dは自身をC ++の改良版ずしお䜍眮付けおいたす少なくずも、この蚘事を読んだ堎合。 いく぀かの叀い構成を砎棄し、埓来のC ++で実装できなかった新しいものを導入し、同時に組み蟌みアセンブラ、ポむンタヌ、Cラむブラリの䜿甚などの䜎レベルの機胜を慎重に保持する機胜により、DはシリヌズCの次のタむトルのナニヌクな候補になりたす-C ++-...」。 たあ、これは私の芳点からです、私自身䞁寧に「残念ながら」远加するのは良いこずですは完党に単䞀蚀語であり、私は長幎C ++で曞いおいお、他の蚀語に粟通する詊みは必然的に良い倢に終わりたした。 しかし、他の信仰の代衚者からは、蚀語ずしおDも圌らにずっお興味深いず聞いたので、ツアヌに参加する党員を招埅したす。



䜕を芋せたすか いく぀かの非垞に良い本がすでにDに曞かれおいるので、暙準ラむブラリからgetopt関数を取り出しおそのコヌドを芋るこずにしたした。これは、本で読んだものを埩掻させる非垞に有益な緎習です。 なぜたさにこの機胜なのでしょうか たあ、圌女は誰にでも銎染みがあり、䜓系的に独立しおいたす。私は個人的に週に3〜4回䜿甚し、3぀の異なる蚀語でどのように曞けるかを詳现に想像しおいたす。 さらに、コヌドの䜜成者はAlexandrescuです。本で圌のコヌドのトレヌニング䟋を䜕床も芋おきたした。本番環境で曞かれたコヌドを芋たこずはありたせんが、興味深いです。 結局、私は確かに抵抗するこずができず、自転車を曞きたした自然に改善されたした。この堎合、それは完党に適切であり、他の人のコヌドを解析するよりも有甚です。



私たちは芋る䟡倀のあるものすべおから遠く離れお芋えるでしょう、そしお私自身は専門家からはほど遠いので、気にするあなた自身のために読んでください、リンクは最埌にありたす。



屋倖怜査

関数の䜿甚方法を瀺すコヌドを次に瀺したす。

void main(string[] args) { // placeholders string file; bool quiet; enum Count { zero, one, two, three }; Count count; int selector; int[] list; string[string] dict; std.getopt.arraySep=","; auto help=getopt(args, , std.getopt.config.bundling , "q|quiet", "opposite of verbose", &quiet , "v|verbose", delegate{quiet=false;} , "o|output", &file , "on", delegate{selector=1;} , "off", delegate{selector=-1;} , std.getopt.config.required, "c|count", "counter", &count , "list", &list , "map", &dict ); if(help.helpWanted) defaultGetoptPrinter("Options:", help.options); }
      
      





最初に衚瀺されるのは「ほがC」です。次に、動的配列-string []およびint []、および連想配列-string [string]の存圚に気付きたす。 次に、いく぀かの疑わしい割り圓お-std.getopt.arraySep = "、" 、それは本圓にグロヌバル倉数です、私たちはkunstkameraに来たしたか、それずもどこですか この動的な連想配列はすべおこの蚀語に存圚し、その基瀎の1぀を構成しおいたす私は個人的には、Perlを蚀葉の意味ですぐに思い出したす。 ただし、 std.getopt.arraySepはモゞュヌルに属する真のグロヌバル倉数であり、 getoptなどの特定の関数であっおも、玔粋䞻矩者の芳点からはおそらくそれを割り圓おるこずは恐ろしいこずです。 ただし、ここではすべおが明確ではないため、 arraySep は関数のペアずしお定矩できたす。



 @property string arraySep() { return ... } @property void arraySep(string separator) { .... }
      
      



最も厳栌なデヌタカプセル化暙準を満たしながら、倉数のように芋えたす 。 これは䞀皮のブランド化されたチップDです-構文糖が完璧になり、蚀語のナニヌクな倖芳を圢成したす。 さらに、この呌び出しは次のようになりたす
 ",".arraySep;
      
      





はるかにフェッチされた倒錯のようですか このデザむンはどうですか

 auto helloWorld="dlrowolleh".reverse.capitalize_at(0).capitalize_at(5).insert_at(5,' ');
      
      





これは圓然、投機的な䟋であり、そのような構文が理にかなっおいるこずを瀺しおいたすが、この構造はDで広く䜿甚されおおり、bashスクリプトのパむプ |蚘号ず同じ成功を収めおいたす。 矎しい名前 Uniform Function Call Syntaxがありたすが、実際にはa.funb、cのようにfuna、b、cを呌び出すこずができるのは構文糖にすぎたせん。

次に、実際の関数呌び出しずむンタヌフェむスの信じられないほどの柔軟性がすぐに目を匕きたす。任意のハンドラヌず説明を含む任意の数の構成パラメヌタヌが関数に盎接枡されたす。 Dが意図せずに動的型付けを行う蚀語であるずいう疑いが朜んでいたす。 埌で芋るように、この皮のものは完璧なテンプレヌト技術ではありたせん。

䞀般的に、オプションの説明は次の行で䞎えられたす。

[修食子、]オプションオプション、[説明、]およびハンドラヌ

ここで最も些现な郚分はオプションであり、「f | foo | x | something-else」ずいう圢匏の単なる行であり、短い同矩語ず長い同矩語の䞡方を定矩したす。 説明構文のヘルプ行も単なる行ですが、もう必芁ありたせん。これには、コンパむル段階での型の䜜業が既に含たれおいたす。

実際の魔法はハンドラヌから始たりたす。アドレスである必芁がありたすが、 enum この堎所では、私の内郚C ++ニックネヌムは私の額にしわが寄っおいたすを含むほずんどすべおのアドレス、および関数たたはラムダ関数のアドレスたあ、それは単玔ですか

詳现



この関数は、2぀の芁玠のタプルを返したす-印刷可胜なオプションのリスト、および論理倉数helpWanted、-hたたは--helpオプションがコマンドラむンに存圚する堎合はtrueリストに自動的に远加されたす。

さお、図を完成させるために、各オプションには、䟋えばrequiredたたはcaseInsensitiveのよう な修食子がありたす。 さらに、モゞュヌルは、optionChar = '-'、endOfOptions = "-"、arraySep = "、"などのいく぀かのグロヌバル倉数を定矩したす。これらの割り圓おにより、コマンドラむン構文が倉曎されたす。

その結果、普遍的で䟿利な関数が埗られたす。これはテンプレヌトであり、C ++で同様のものを実装する方法はほが明らかですが、Dでどのように実行されるのでしょうか。



フヌドを開きたす

最初に泚意を匕くのは、テンプレヌト関数を定矩する非垞にシンプルで自然な方法です。通垞関数ずテンプレヌト関数の構文の違いは、知芚を倉えるほど埮劙です。「通垞」関数ず「テンプレヌト」関数ではなく、テンプレヌトになりたす。 今埌、 opts匕数には配列ずしおアクセスできたす-opts [0]、opts [$-1]たたはopts [2..5];

 GetoptResult getopt(T...)(ref string[] args, T opts) { ... getoptImpl(args, cfg, rslt, opts); return rslt; }
      
      





実際、トップレベルの関数に぀いおは、制埡をすぐにgetoptImplに転送するため、これ以䞊説明するこずはありたせん。

  1 private void getoptImpl(T...)(ref string[] args, ref configuration cfg, ref GetoptResult rslt, T opts) 2 { 5 static if(opts.length) { 6 static if(is(typeof(opts[0]) : config)) { 7 // it's a configuration flag, act on it 8 setConfig(cfg, opts[0]); 9 return getoptImpl(args, cfg, rslt, opts[1 .. $]); 10 } else { 11 // it's an option string ... 16 static if(is(typeof(opts[1]) : string)) { 17 auto receiver=opts[2]; 18 optionHelp.help=opts[1]; 19 immutable lowSliceIdx=3; 20 } else { 21 auto receiver=opts[1]; 22 immutable lowSliceIdx=2; 23 } ... 34 bool optWasHandled=handleOption(option, receiver, args, cfg, incremental); 41 return getoptImpl(args, cfg, rslt, opts[lowSliceIdx .. $]); 42 } 43 } else { 44 // no more options to look for, potentially some arguments left ... 68 } 75 } 76 }
      
      





数字でわかるように、私はかなりの数の行を捚おたしたが、このコヌドの構造党䜓が完党に芋えおいたした。

最初に泚意を匕くのは、 静的if{} else static if{} else {}の構築です。はい、これはたさにあなたが考えおいたものです。 コンパむル時に静的if匏が遞択されるブランチは、圓然、 コンパむル時に条件も認識されおいる必芁がありたす。 したがっお、コンパむル䞭のこのコヌド私の奜みに合わせおスパゲッティを少し配るは、この特定の関数匕数セットに意味のあるいく぀かの行にカットされたす。 すでに述べたように、テンプレヌトパラメヌタヌは䞍倉配列のように扱うこずができたす。オプションのリストが空の堎合、 opts.lengthが0を返す堎合は静的なので、43行目のコヌドはこの堎合のテンプレヌトの特殊化を眮き換えたす。

もう1぀の興味深い点は、 静的ifがスコヌプを倉曎しない堎合 、䞭括匧の埌を芋おください

 16 static if() { 19 immutable lowSliceIdx=3; 20 } else { 22 immutable lowSliceIdx=2; 23 } 41 return getoptImpl(args, cfg, rslt, opts[lowSliceIdx .. $]);
      
      





lowSliceIdx倉数はブロックの1぀で定矩されおいたすが、それらはブロックの倖で䜿甚されたす。私の意芋では非垞に論理的です。 この倉数は䞍倉= constexprずしお定矩されおいるため、コンパむル時にも䜿甚でき、テンプレヌトで䜿甚できたす。

オプションの分析ず型の実際の䜜業が始たる堎所を詳しく芋おみたしょう。

  6 static if( is(typeof(opts[0]) : config)) { 7 // it's a configuration flag, act on it 8 setConfig(cfg, opts[0]); 9 return getoptImpl(args, cfg, rslt, opts[1 .. $]); 10 } else { ...... 42 }
      
      





ああ、そこにある Dでは、C ++で埅望のtypeofexprを䜜成し、意図したずおりに機胜したす。 しかし、それだけではありたせん。匏はT == Uです。型TずUが等しい堎合もちろんコンパむル䞭に、テンプレヌトパラメヌタヌおよびその他のナヌスケヌスを䜿甚する堎合にのみ、タむプ。 䞀般的に、 isは、匕数がいずれかの型である堎合、぀たり匏が構文的に正しい堎合にのみtrueを返すむンラむンSFINAEです。 たずえば、 isarg == U []、Uはargが配列であるこずを確認し、 argint -argは自動的にintに倉換できたす。コロンは控えめに継承を瀺唆したす。 埌でさらに䟋がありたす。 したがっお、6行目の匏は、最初のパラメヌタヌのタむプ typeofopt [0]が特定のタむプのconfigにキャストされおいるかどうかを静的にチェックしたす。configは、可胜なすべおのオプション修食子の単なる列挙です

 enum config { /// Turns case sensitivity on caseSensitive, /// Turns case sensitivity off caseInsensitive, /// Turns bundling on bundling, /// Turns bundling off noBundling, /// Pass unrecognized arguments through passThrough, /// Signal unrecognized arguments as errors noPassThrough, /// Stop at first argument that does not look like an option stopOnFirstNonOption, /// Do not erase the endOfOptions separator from args keepEndOfOptions, /// Makes the next option a required option required }
      
      





その埌、 getoptImplは修食子の倀を保存し保存+倀=>ランタむム、オプションから最初の匕数 opt [1 .. $] を削陀するこずで再垰的に自身を再呌び出ししたす。 したがっお、型凊理の最初のケヌスを考え出したずころ、驚くほど単玔でした。 これらの無限のコンパむル時間/ランタむムを頭の倖に投げおコヌドをそのたた読んで、 typeofTに出䌚ったずき、このタむプが定矩されおいる堎所たでのペヌゞを怜玢したすこの堎合、実際のgetoptパラメヌタヌのリストでは、䞍快です C ++では、魔法のように芋えたすが、それずも意図したものでしょうか結局のずころ、コンパむラヌは入力コヌドの圢で私ず同じ情報をすべお持っおいたす。

次に、入力配列から芁玠を1぀ず぀再垰的に取り出すず、コンパむラは最初の文字列パラメヌタヌを取埗したす。このパラメヌタヌは、このオプションのタグのリストである必芁がありたす。11行目。ここからオプションを開始したすが、これも非垞に簡単に解決されたす。2番目の次のパラメヌタヌが文字列の堎合、これは説明であり、3番目はそれぞれハンドラヌのアドレスです。 それ以倖の堎合ストリングではない、これはハンドラヌです。 したがっお、リストから3぀たたは2぀のパラメヌタヌを取り出しお、次の関数handleOptionに枡したす。この関数はコマンドラむン自䜓を既に解析し、自然に再垰的に呌び出しお、すべおが最初からやり盎したす。

すでに芋たものず比范しお、次に䜕も新しいこずは起こりたせん。 handleOption関数は、単䞀のパラメヌタヌハンドラヌのタむプを持぀テンプレヌトで、コマンドラむン党䜓を通過し、説明に適合するかどうかを確認し、芋぀かった堎合はそのハンドラヌに察応するアクションを実行したす。 私の芳点から最も興味深い点を簡単に怜蚎したす。

たず、䞀般的な䞊面図

 static if(is(typeof(*receiver) == bool)) { *receiver=true; } else { // non-boolean option, which might include an argument static if(is(typeof(*receiver) == enum)) { *receiver=to!(typeof(*receiver))(val); } else static if(is(typeof(*receiver) : real)) { *receiver=to!(typeof(*receiver))(val); } else static if(is(typeof(*receiver) == string)) { *receiver=to!(typeof(*receiver))(val); } else static if(is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) { // functor with two, one or no parameters static if(is(typeof(receiver("", "")) : void)) { receiver(option, val); } else static if(is(typeof(receiver("")) : void)) { receiver(option); } else { static assert(is(typeof(receiver()) : void)); receiver(); } } else static if(isArray!(typeof(*receiver))) { foreach (elem; ...) *receiver ~= elem; } else static if(isAssociativeArray!(typeof(*receiver))) { foreach (k, v; ...) (*receiver)[k]=v; } else { static assert(false, "Dunno how to deal with type " ~ typeof(receiver).stringof); } }
      
      





繰り返し可胜なデザむン

 static if(is(typeof(*receiver) == ...)) { *receiver=to!(typeof(*receiver))(val);
      
      



実際には、「䜕かぞのポむンタヌがハンドラヌずしお枡される堎合、匕数をこのタむプに倉換しお、ポむンタヌに割り圓おおみおください」ずいう意味です。

匕数を持たないboolぞの個別に凊理されたポむンタヌ。 匕数がコンテナに远加される配列および連想配列。 同様に、1぀、2぀、たたは匕数を持たない関数ずラムダ関数。 内郚関数タむプセレクタヌに泚意しおください

  static if(is(typeof(receiver("", "")) : void)) { receiver(option, val); } else static if(is(typeof(receiver("")) : void)) { receiver(option); } else { static assert(is(typeof(receiver()) : void)); receiver(); }
      
      





これは、匏isTのもう1぀の䜿甚䟋であり、Tが既存の型である堎合にのみ真になりたす 。 この特定のケヌスでは、関数によっお返される型を調べたす*レシヌバヌ、*レシヌバヌ ""たたは*レシヌバヌ ""、 "" 、関数の眲名が存圚する堎合、型も存圚したす。 SFINAE  voidはフルタむプです

たた、 std.convモゞュヌルからナニバヌサルDコンバヌタヌに慣れるのも䟿利です toT<lexical> 、 boost :: lexical_castのように機胜したすが、それずは察照的に、Dは䞊蚘のコヌドでわかるように、コンパむル時間。

それだけで、玄400行の意味のあるコヌド行にかなり耇雑な関数が実装され、その結果、C ++での再珟は可胜な限り非垞に困難になりたす。 さお、順番に、Dで型を操䜜する機胜-可倉数の匕数を持぀テンプレヌト関数、コンパむル時の型ずコヌド分岐の遞択、および型倉換の機胜に粟通したした。 実際、これはDが開発者に提䟛するツヌルのごく䞀郚にすぎず、サむトにはさたざたなトピックに関する膚倧な蚘事のコレクションがありたす。 Dに切り替えるこずやDを孊ぶこずをだれにも勧めないが、奜奇心ず新しいこずに興味を持っおいるなら、これは少なくずも衚面的に知っおおくべき蚀語である。



玔粋な理由の批刀

しかし、私は批刀に抵抗するこずはできたせん、私は間違いなく提案された実装で䜕かが奜きではありたせん。 抂しお、これは蚀語自䜓ずは䜕の関係もありたせんが、䞀般的なプログラミングの芳点から議論するこずは興味深いものです。

たず、この実装はsingle-passになりたす。぀たり、オプションがリストから取埗され、コマンドラむンパスがすぐに䜜成され、最初に芋぀かった䞀臎がルヌプを䞭断したす。 これは、 -qqqを「静かで静かな、さらに静かな」の同矩語ずしお蚘述できないこず、たたは--map A = 1の代わりに--map A = 1 --map B = 2 --map C = 3を蚘述できないこずを意味したす。 2、C = 3 これは䞀般にバグではありたせんが、 getoptを䜿甚する際に確立された芏則に違反するため、埓来の動䜜を確認したいず思いたす。

第二に、これはすでに私の意芋では深刻なアヌキテクチャ䞊の間違いです。関数は構文ヘルプを䜿甚しお特定の構造を返したす。これは通垞-h | --helpキヌを䜿甚しお出力されたすが、この同じ関数ぱラヌの堎合に䟋倖をスロヌしたす ぀たり、コマンドラむンでミスをするず、プログラムはその方法を䌝えるこずができなくなりたす。 䞀般的に蚀えば、これは同じシングルパス実装から来おいたす。
UPDAlexandrescuはHabrを読みたすか
前回のコミットでは、これは修正されたしたが、私がしたように完党には修正されたせんでしたが、それでも修正されたした。
さらに、いく぀かの小さな欠陥がありたす。たずえば、オプションには奜きなだけ同矩語を含めるこずができたすが、最初の2぀だけが構文ヘルプに該圓したす。オプション「x | abscissa | initialX」では、最埌の倀はコヌドを調べるこずによっおのみ芋぀けるこずができたす さお、いらいらするささいなこず。

したがっお、私は挔習ずしお独自の実装を行い、これらの欠点を修正し、さたざたな添えものを䜜成したした排他的に挔習ずしお、䞀般的に私は望んでいたように楜しみたした。

ここに私の自転車がありたした 私の自転車はどこですか

しかし、いや、自転車は今そこにありたす 、それは起こりたす。 良いガむドがどこに泊たるかを知っおおくず決めたので、これでツアヌは終了です。

おもしろかった



曞誌

最初の3冊の本を読んだので、それぞれに別々の番号を付けたした。 それらはすべお良いですが、私の奜みはどれも完璧ではないので、サンドむッチから1぀の章を読み、察応する他の2぀の章を読みたした。

  1. おそらく歎史的に最初のDに関する本
  2. 本アレクサンドレスク
  3. ずおも玠敵な料理の本
  4. Dに関する他のすべおの既存の本
  5. 曞籍よりもさらに興味深い蚘事の集たりですが、それぞれが別々のトピックになっおいたす

  6. りィキは蚘事ず同じくらい面癜いですが、残念ながらあなたはあなたが探しおいるものを知る必芁がありたす

  7. メむンサむトD
  8. ロシア語版も存圚したす
  9. githubの暙準ラむブラリ。 ゜ヌスは同じ堎所でgetoptしたす。




All Articles