C ++テンプレヌトに倉換する難解な蚀語

コヌド䟋付きのKPDV C ++テンプレヌトは、コンパむル時プログラムを䜜成できるチュヌリング完党蚀語です。 珟圚、構文はパラメヌタ化された型を蚘述するように蚭蚈されおおり、より耇雑な䜕かの明確な衚珟にはあたり適合しおいたせん。 この蚘事では、型ずパタヌンがどのように倀ず関数になるのか、そしお著者がC ++テンプレヌトに翻蚳された独自の関数型蚀語を䜜成しようず詊みたものを調べたす。 テキストを読むには、関数型プログラミングの分野の知識はほずんど必芁ありたせん。

どのように機胜したすか


ここでは、プログラムテンプレヌトからコンパむル段階を構築するための基本的なブリックを配眮したす。 関数型プログラミングに慣れおいない人のために、必芁な抂念を簡単な蚀語で説明したす。 基本的なこずを怜蚎したら、新しい蚀語、その構文、機胜、および問題に進む準備ができおいたす。 すぐに䞻芁なものに盎接行きたい人は、蚘事タむトルの再衚瀺の暪にある緑色の写真にスクロヌルできたす。

正盎なずころ、数幎前、私はここで、匏の挔算子の優先順䜍を倉曎するアルゎリズムの䟋を䜿甚しお、コンパむル時プログラムの原理を説明する投皿を曞き蟌もうずしおいたした぀たり、 2+2∗2別の優先順䜍で「再構築」し、その方法を蚈算する 8しかしではない 6

長い蚘事を写真ずUMLダむアグラムでほが終えたしたが、突然、メタプログラムがハブで頻繁に蚀われるこずに気付きたした。 さらに、実際の䜿甚のためにconstexprを远加したしたが、私は倒錯した゚ンタヌテむメントの分野に留たり、最小限の蚀語機胜を䜿甚したかったのです。 泚目すべき蚘事HurrTheDurrからのコンパむル時の生掻ずコンパむル時の 解釈、たたはilammyからのC ++ 11のラムダの代替理解 2぀目はよりハヌドコアで、 constexpr using templateをusing templateないは、倒錯した心が知る必芁があるほがすべおを説明したした。 その結果、蚘事をドラフトコピヌのたた残したしたが、メタプログラミングぞの欲求は残したせんでした。 そしお今日、私は新しく曞かれたテキストず新しいアむデアで戻っおきおいたす。

私の努力にもかかわらず、読者が玠材を理解するのがただ難しい堎合は、これらの蚘事を読むこずをお勧めしたす-その埌、Paul Hudack et al。からのHaskellの簡単な玹介 パヌト1 、 パヌト2 Denis Moskvin and Lambda calculus by ibessonovによる JavaScript 。 このすべおがある皋床著者に圱響を䞎え、おそらく読者が圱響を受けるでしょうか

メタ倀ずメタ関数


クラステンプレヌトは、ある意味では、型を受け入れお返す型の関数です。 したがっお、パタヌンはメタ関数になり、タむプず敎数はメタ倀になりたす。 たずえば、メタ関数std::vectorはメタ倀Tを取り、メタ倀std::vector<T>を返したす。 メタ倀には、䞀連のメタフィヌルドC ++の芳点から-ネストされたクラス、型゚むリアス、静的定数フィヌルドがあり、それによっお最も興味深いこずができるこずが重芁です。

valueなどのメタフィヌルドを1぀遞択し、メタ関数が返す倀ずしお理解するこずができたす。 敎数䞊のメタ関数は非垞に簡単に曞くこずができたす

 template <int x> struct square { static const int value = x * x; }; 

メタ関数は、FPで次のように呌び出されたす square<5>::value 。

しかし、敎数は通垞の倀であり、単玔なC ++で十分なので、敎数をメタ匏で䜿甚するのはややスポヌツマンらしくないでしょう。 真の正盎なメタはタむプです。 それを䜜成する最も簡単な方法は、構造䜓を宣蚀するこずです

 struct One; struct Zero; 

C ++の芳点から芋るず、 oneずzero 宣蚀されお zeroだけで、実際にzeroできたせん。 これで十分ですか はい それでは䜕が等しく、それらをどのように䜿甚するのですか 圌らは平等であり、それは私たち自身にずっおのみ重芁です。 これらは、シンボリック蚈算で䜿甚できる抜象的なシンボルの䞀皮ですMathematicaやその他のものずほずんど同じです。 メタプログラムは、さたざたな耇雑さのメタ匏を蚈算したす。 最初にこれらの衚珟を怜蚎し、少し埌にシンボルの解釈ず結果の衚瀺を扱いたす。

ブヌル倀ずしお0ず1の堎合、NOT、AND、およびOR関数を蚘述するのは自然です。 吊定のメタ機胜を考慮しおください。

 template <typename T> struct Not { typedef Zero value; }; template <> struct Not <Zero> { typedef One value; }; 

倀を取りNot 。この倀がれロの堎合は1を返したす。 それ以倖の堎合はすべお、れロを返したす。 したがっお、テンプレヌトの特殊化により、初期段階ではパタヌンマッチングサンプルずの比范がありたす。特定の倀を持぀1぀以䞊の匕数に察する関数の動䜜を個別に蚘述するこずができたす。必芁な専門化。 これを䜿甚しお、すでに再垰的なものを曞くこずができたすたずえば、階乗、説明をfac<0>ずfac< >に分割する。

䞀芧


デフォルトvalue制限されおいないvalue 、倚倀関数がどのように芋えるか想像できたす。 関数型プログラミングの専門家になじみのあるConsリストコンストラクタヌず空のNilリストを䜜成したしょう。

 template <typename h, typename t> struct Cons { typedef h head; typedef t tail; }; struct Nil; 

FPのConsは、最初の芁玠のリスト先頭ず他の芁玠のリスト末尟を䜜成する関数です。 通垞リスト \ {1、2、3 \} Cons<one, Cons<two, Cons<three, Nil>>>䞀臎したす。 リストを操䜜するには、そのコンポヌネントを取埗できる必芁があるため、 Cons 、ヘッド Cons<...,...>::head ずテヌル Cons<...,...>::tailを返す倚倀関数にしたすCons<...,...>::tail 。 OOP愛奜家は、 Consがコンストラクタヌであり、 headずtailがgetterであるこずを想像できたす。 FIでは、すべおの割り圓おは、倉曎された匕数を䜿甚した関数のアプリケヌションの呌び出しに眮き換えられるため、セッタヌずその類䌌物はありたせん。

関数型蚀語にはルヌプがないため、このようなリストは通垞​​、再垰的に実行されたす。

 //    template <typename list> struct negate { private: //    typedef typename list::head h; //  typedef typename Not<h>::value not_head; //   typedef typename list::tail t; //  typedef typename negate<t>::value not_tail; //   public: //     -   , //     typedef Cons<not_head, not_tail> value; }; //    template <> struct negate <Nil> { //   -    typedef Nil value; }; 

Haskellはペむントほど怖くないこずがわかりたした。 どれも非垞にシンプルに芋えるので、この蚀語で䟋を明確にするために䟋を远加するこずは眪ではありたせん。 準備ができおいない読者のために、C ++や孊校の数孊ずの䞻な違いに泚意しおください。Haskellでは、匕数はスペヌスで区切られ、括匧でグルヌプ化されたせん。 すなわち fx+y、gy、zHaskellではf (x+y) (gy) zずしお蚘述されたす。

 --  -      (  -  ), --   data List a = Cons a List | Nil --       head (Cons x _) = x --   tail (Cons _ xs) = xs --   --   negate (Cons x xs) = Cons (Not x) (negate xs) negate Nil = Nil 

匷く型付けされたHaskellやC ++ずは異なり、アヒルの型付けはテンプレヌト蚀語で機胜したす。 negate<One>::valueはもちろん動䜜したせんが、 Oneにheadずtailメタフィヌルドがある堎合は動䜜したす。 ただし、 negate<One> ::value negate<One> 「参照解陀」されnegate<One>いない間、プログラムはコンパむルを続行したす。

高階関数


関数型蚀語では、関数は同じ意味を持ちたす。 したがっお、高次関数を曞くこずができたす-関数を匕数ずしお受け取るか、関数を返したす。 たずえば、リストの芁玠ごずの倉換はFWPマップを䜿甚しお実行されたす。

 --       map f (Cons x xs) = Cons (fx) (map f xs) --      map f Nil = Nil 

STLには、 std::transformず呌ばれるこのような倉換が含たれおいたす。 この蚀語では、 mapメタ関数は、テンプレヌトによるテンプレヌトパラメヌタヌ化を䜿甚しお宣蚀されたす。

 //    // f -     -   template <template <typename> class f, typename list> struct map { private: typedef typename list::head h; //  typedef typename f<h>::value new_head; //   typedef typename list::tail t; //  typedef typename map<f, t>::value new_tail; //   public: //       typedef Cons<new_head, new_tail> value; }; //    template <template <typename> class f> struct map<f, Nil> { //      typedef Nil value; }; 

fに぀いおはfここで前に説明したNot関数に眮き換えお、ネガのリストを蚈算できたす。

 typedef map<Not, Cons<One, Cons<One, Cons<Zero, Nil>>>>::value list; // list  Cons<Zero, Cons<Zero, Cons<One, Nil>>> 

匏の呜名操䜜


typedefは代入挔算子ず同等であるこずがtypedef 。 たたは、関数型蚀語の方が正しいず思われるのは、名前ず匏の察応を指定する操䜜です。

C ++ 11以降では、型ずテンプレヌトの゚むリアスを䜿甚しお、既知の匏を通じお倀ず関数を蚭定できたす。

 using x = Not<One>::value; template <typename xs> using g = Cons<x, xs>; 

メタ関数が単䞀のメタ倀を返す堎合、テンプレヌト゚むリアスはvalueメタフィヌルドを削陀したす。 これは、プログラマヌが::value明瀺的に指定するのが面倒で、蚈算可胜性の芁件を課す堎合により䟿利です。

プログラムは単玔に無限再垰に入るこずができたす
negate<Zero>コンパむルされたすが、 negate<Zero>::valueはコンパむルされないこずを思い出しおください。 面倒なメタフィヌルドを䜿甚しお、条件に応じおブランチの1぀だけの::valueを蚈算し、この倀を返すブランチメタ関数を䜜成できたす。 匏の1぀が評䟡されるこずはありたせん。すべおの操䜜は::value受け取ったずきにのみ実行され、誰もそれに觊れたせんでした。
 //      template <typename expr1, typename expr2> struct If <True, expr1, expr2> { // expr1, expr2 ,  expr1::value  expr2::value -  typedef typename expr1::value value; } // If<One, id<One>, destroy<the_world>>::value    

同時に、テンプレヌト゚むリアスを持぀バリアントは、䞀郚のg<x>が既に蚈算されたf<x>::valueの意味を持぀こずを提䟛しf<x>::value 。 たた、2぀の再垰オプション間で分岐する堎合、蚈算は無限になりたす。これは、コンパむル段階でのスタックオヌバヌフロヌに盞圓したす。

 template <typename expr1, typename expr2> struct If <True, expr1, expr2> { // expr1, expr2  typedef expr1 value; }; // If<One, One, destroy<the_world>::value>::value   


基本的に、テンプレヌト゚むリアスはテンプレヌトに盞圓する単なる構文糖であり、省くこずができたす。

短絡


通垞のC / C ++では、関数が完了した埌に匕数ずロヌカル倉数が死ぬ堎合、関数型蚀語では、関数は芪関数の匕数ずロヌカル倉数に䟝存する関数を返すこずができたす。 したがっお、芪関数はすでに完了しおいたすが、そのロヌカル倉数は子関数に「アタッチ」されたたた残りたす。 実際には、これは、たずえば単項関数などのむンタヌフェむスが必芁な堎合に䟿利であり、その実装には䟝存するコンテキストもありたす。

たずえば、 fxは、関数f匕数x暗黙的に䜿甚しお、単項関数g返したす。

 fx = g where g xs = Cons x xs -- : (fx) xs ,  , map (fx) list 

Consを残すこずもできたすが、 gは既に蚘述されおいるmap関数に単項ずしお枡すこずができたすが、 ConsはバむナリでConsたせん

ただし、通垞のC ++はクロヌゞャの欠劂に悩たされたせん
これを行うには、機胜オブゞェクトたたはラムダ関数を䜿甚したす。 ぀たり、グロヌバル倉数を介しお関数に远加のパラメヌタヌを枡すず、関数を䜿甚する代わりにプログラムの柔軟性に違反する堎合、オブゞェクトが䜿甚されたす。 thisには必芁なコンテキストがすべお含たれ、 operator ()はむンタヌフェむスに必芁な匕数を取りたす FPの矎しいコンセプトは、同等のOOPコンセプトに眮き換えられおいたす。 暙準std::functionテンプレヌトも提䟛されおいたす

 //  C++11:  ,   struct f { f(something& x) : x(x) {} something_else operator () (something_else& xs) { // xs -   return Cons(x, xs); } private: something x; //   }; // C++11  : -,   something x; auto f = [x](something_else& xs) { return Cons(x, xs); }; 


関数たずえば、 int(int) を機胜オブゞェクトたずえば、 std::function<int(int)> に眮き換えるず、C ++プログラマヌは、クロヌゞャヌの本栌的なアナログを取埗したす。

テンプレヌトレベルでは、「関数→オブゞェクト」を眮き換える必芁はありたせん。 クロヌゞャは蚀語自䜓によっおサポヌトされおいたす

 template <typename x> struct f { template <typename xs> struct g { typedef Cons<x, xs> value; }; }; // : f<x>::g<list>::value  map<f<x>::g, list> 

C ++やHaskellずは異なり、ここではf文字はg 「取り出し」たすが、残りは正盎なクロヌゞャヌです。 匏f<x>::gは、通垞の単項関数たずえば、 Not の代わりに䜿甚できたす。

リストの無制限の匕数たたは構文糖


可倉長テンプレヌトを䜿甚するず、リストから関数を蚘録できたす。 繰り返したすが、堎合によっおはより䟿利ですが、それらがなければConsずNilず平和に暮らすこずもできたす。 さらに、これはさらに単玔な動きになるこずがありたす。 2぀のリストをメタ関数に枡すには、2぀のリストを枡すだけで十分です f<xs, ys> 、可倉個のテンプレヌトの堎合、最埌のリストを陀くすべおのリストをキャプチャするラッパヌを指定する必芁がありたす f<list_wrapper<x1,x2,x3>, y1, y2, y3> 。これは、任意の長さの匕数のリストは1぀である必芁があり、行に蚘述されたいく぀かのリストは単に「結合」するためです。 蚈算の最埌に、ラッパヌを返す必芁がありたす。 C ++では、いく぀かのタむプのリストを入力するこずはできたせん。 そしお、ラッパヌからリストを「取り出し」おメタ関数たずえばf に転送するには、メタコヌルバックを䜿甚する必芁がありたす。このメタ関数fを受け入れ、リストの内容を枡すメタメ゜ッドをラッパヌで実装したす。

 typename <typename... xs> struct list_wrapper { //   list_wrapper<...>::call , //     xs struct call<template <typename...> class f> { typedef typename f<xs...>::value value; }; }; 

反埩/再垰リスト凊理は、これを䜿甚しおも実装するこずはできたせん。 たずえば、 negateを実装negateは、補助単項メタ関数fを䜜成する必芁がありたす。これは、リストの末尟に再垰的にnegateを適甚した結果を受け取り、ヘッドの吊定を蚈算し、リストのラッパヌを返したす。

 typename <typename x, typename... xs> struct negate { template <typename... ys> struct f { typedef list_wrapper<Not<x>::value, ys> value; }; typedef typename negate<xs>::template call<f>::value; }; typename <> struct negate<> { typedef list_wrapper<> value; }; 

フォヌムの矎しい蚘録のためにそれが刀明したした \ {x1、x2、x3 \} 発衚時に、これらのシヌケンスをパック、転送、およびアンパックする必芁がありたした。 negateは、 Cons / Nilバヌゞョンず比范しおも、かさばりたす。 ここでは、FPの基本ずHaskell→C ++の機械的亀換で「通垞の」バヌゞョンを䜜成するのに十分な堎合、より深刻な補造が必芁になりたす。 そのため、可倉テンプレヌトを䜿甚しお、パラメヌタヌのシヌケンスをCons / Nilリストに倉換するラッパヌを䜜成し、プログラムを実装するずきにそれを䜿甚するこずをお勧めしたす。 そのため、快適なコンマ区切りリストを䜿甚しおリストを蚭定し、より単玔なカテゎリヌで考えるこずができたす。

倖の䞖界ぞの出口


これたで、倀ず玔粋な関数のみを考慮しおきたした適甚の結果は匕数のみに完党に䟝存し、同じ匕数に察しおは関数は同じ倀を䞎えたす。 プログラムの結果を出力するには、コンパむル段階で玔粋な関数ず蚈算の䞡方を䜿甚する必芁がありたす。

メタプログラムでの蚈算が完了するず、結果の匏は実䞖界の゚ンティティずしお解釈され、倉数に保存されるか、画面に衚瀺されたす。 出力にテンプレヌトクラスを䜿甚できたす。 コンストラクタヌたたは他のメ゜ッドには、呜什コヌドが含たれおいたす。 パタヌンマッチング機胜がすべおのオプションを怜蚎するのに十分な堎合、メタ倀自䜓たずえば、 void One::print(); 内、たたはメタ関数内のいずれかになりたす。 ここで、たずえば、 print<...>むンスタンスを構築する段階で、匕数単䜍、れロ、たたはリストを出力するprintメタ関数

 template <typename T> struct print { print () { std::cout << "unknown number" << std::endl; } }; template <> struct print <One> { print () { std::cout << "1" << std::endl; } }; template <> struct print <Zero> { print () { std::cout << "0" << std::endl; } }; // print<Not<Zero>::value>()  "1" 

同様に、リストや必芁なものを衚瀺できたす。 たずえば、AND、NOT、XOR、バむナリ加算噚を実装し、数字をOneずZeroリストずしお衚珟し、加算、乗算などを構築できたす。

C ++ 11およびdecltypeの登堎以前は、型に察しお玔粋に機胜的な蚈算を実行し、察応する型の倉数およびC ++オブゞェクトを䜜成し、それらに察しお蚈算を実行しおから、再び型に察する蚈算に戻るこずは䞍可胜でした。

 sum<sum<One, two>, three> //  -  sum<sum<One, two>, three>() //  -  do_something(sum<One, two>(), three()) //  -  sum<sum<One, two>(), three()> //  , // ..       sum<decltype(sum<One, two>()), decltype(three())> // C++11;  

もちろん、この機胜により、より䜎コストでC ++でむデオロギヌ的に正確なコヌドを蚘述できたしたが、テンプレヌトのメタ蚀語のむデオロギヌはわずかに揺れたした。 たずえば、Haskellでは、呜什型䞖界の有毒カップを飲んだ関数は、それによっお氞遠に毒されたたたであり、玔粋な䞖界に属する通垞の倀を返す暩利がありたせん。 ぀たり、機胜的な玔床から呜什型ぞの移行は、䞀方向にのみ行うこずができたす。

decltypeするdecltype型は玔床の類掚であり、倀は呜什型の䞖界の本質でした。型のみがテンプレヌトパラメヌタになり、型は型倉換によっおのみ取埗できたした。 倀を䜜成するず、型に戻るこずは䞍可胜でした。

C ++ 11では、型を蚈算し、この型の倀を䜜成し、䜕らかの方法で倉換し、 decltypeを䜿甚しお結果型を型の匏に枡すこずがdecltypeたす。 ただし、 decltypeは匕数をdecltypeせず、機胜の玔床に違反しない「カりントを開始した堎合の衚珟のタむプ」ずいう質問にのみ答えたす。 したがっお、倉数の匏がdecltypeブラケットを離れるたで、枅朔さが維持されたす。 テンプレヌト蚀語の芳点から、 decltype挔算子のオヌバヌロヌドずずもに䜿甚するdecltype有益です。 次の䟋では、匏は同等ですが、䞋郚はかさばっおいたせん。

 typedef sum<sum<one, two>, three> x; typedef decltype(one() + two() + three()) x; 

かっこから出るず、枅朔さに違反したす。

 auto v = one() + two() + three(); // v     typedef decltype(v) x; 

C ++テンプレヌトに倉換する難解な蚀語


コンパむル時FJずしおのC ++テンプレヌトの䞻な問題は、過床の冗長性です。 これらすべおの::value 、角括匧、およびtypenameは、プログラマを匷く䜿い果たし、プログラムコヌドを拡匵したす。 もちろん、このスタむルでプログラミングするこずを決めた人は苊しむべきだずロゞックは蚀っおいたすが、...これはIT担圓者の脳がしばしば傟いおいる異垞な゚ンタヌテむメントの1぀です。 倒錯が保存されるこずを確認したいず思いたすが、苊しみがただ耐えられる皋床に。



䞀般に、私は独自の蚀語を䜜成し、C ++テンプレヌトに翻蚳するこずにしたした。 これを行うには倚くの方法がありたす。 JSFuckトランスレヌタヌによっお行われたような最も独創的な倉換を䜿甚するこずもできたす。 しかし、そのようなアプロヌチは、a別の䞍必芁な蚀語を生成し、bC ++から離婚し、cただ発明する必芁があり、d実装に゚ネルギヌを費やしたす。 そしお、十分に匷力で䞍必芁なFYを開発しお実装するこずは、面倒で圹に立たないビゞネスです。 特に、C ++テンプレヌトが既に関数、クロヌゞャ、パタヌンマッチングに盞圓する構造を持っおいる堎合...そしおそれはスポヌツではありたせん。

私は最倧の察応の道を遞択したした。 私の蚀語のコヌドはシンプルに芋えるはずですが、むデオロギヌ的にはC ++に䌌おいたす。 ぀たり、倉換により、私の蚀語はC ++で最も逐語的に翻蚳されたはずです。 これは、パタヌンを超える1キログラムの構文䞊の砂糖であるず想定されおいたした。

䞊蚘のコヌドを䜜成した蚀語に曞き盎し、その機胜ず実際のアプリケヌションを調べおみたしょう。「れロ」ず「1」の倀の決定、数を吊定する関数、リストのコンストラクタヌ、リストを吊定する関数の定矩、FVPマップなどの決定から同じ順序で移動したす。

倀ず関数


structキヌワヌドは、倀を宣蚀するために必須ではありたせん。

 One; Zero; 

関数宣蚀は、匕数ずその型を䞭括匧で囲み、「=」蚘号の埌ろに本文を残したす。特定のケヌスの説明は、䞀般的なケヌスの説明の埌にありたすが、匕数の具䜓的な倀はタむプを瀺しおいない点が異なりたす。

 Not (val x) = Zero; //   Not (Zero) = One; //   And(val x, val y); //  x,y != One,Zero And   And(val x, One) = x; // One - , val x -  And(val x, Zero) = Zero; // : Not(One)  And(One, Zero) 

C ++では、テンプレヌトパラメヌタはタむプtypename、別のテンプレヌトtemplate <typename> classなどにするこずができ、これを指定する必芁がありたす。したがっお、meta-FVPを䜜成typenameするこずはできたせん。たた、タむプを瀺す芁件は私の蚀語にも適甚されたす。デフォルトではval、通垞のメタ倀に察応するタむプが蚭定されたすC ++の構造たたは通垞のタむプ。関数を蚘述するために、タむプを矢印->ず組み合わせるこずができたす。たずえば、val -> val-単項関数1぀のパラメヌタヌを取るテンプレヌト(val, val) -> val-バむナリヌ関数2぀のパラメヌタヌを取るテンプレヌトなど。

ここで、翻蚳のリテラル性から少し逞脱し、タむプ゚むリアスを導入したした。これは、C ++のタむプ゚むリアスずは異なりたす。を䜿甚しお#type 同矩語を蚭定しお、レコヌドをもう少し短くし、適切な名前を付けお意味を明確にするこずができたす。

 #type number = val; #type list = val; #type unary = number -> number; #type map_t = (unary, list) -> list; // map_t   template <template <typename> class, typename> class 

Cの#type類䌌物ず芋なすこずができ#define、簡単な方法で手動で実行できるテキスト倉換を定矩したす。

䞀芧


䟿利な機胜ずしお耇数の返品をキャンセルしおいたせん。これは、リストを実装するずきに䟿利です。

 Nil; Cons(val x, val y) { head = x; tail = y; } 

C ++で翻蚳された関数を匕数に適甚するず、自動的に展開され::valueたす。それはf(x)同等f<x>::valueです。しかしCons、2぀のメタポヌルのみheadが生成されtailたす。開瀺を防ぐために::valueアポストロフィを䜿甚しお明瀺的に必芁ずされおいたすCons(x, xs)'。

私はこの問題に぀いお長い間考えおいたした。䞀方で::valueは、頻繁に䜿甚される構造の1぀が自動的に開かれるべきでしたが、a開瀺されおいない倀を送信しスポむラヌの䞋で䞊蚘の分岐関数ず無限再垰の問題を参照、b以倖のメタフィヌルドを䜿甚する必芁がありたしたvalue。その結果、私はピリオドを介しおメタポヌルを曞く「゚スケヌプ」に萜ち着き、逆挔算子「」を導入したした::value。

 Cons(x, xs)'.head; // x Cons(x, xs)'.tail; // xs Not(Zero); // One Not(Zero)'!; // One 

ただし、メタポヌル::valueは耇数のリタヌンず共存する可胜性がありたす。negateC ++でリストの吊定を実珟し、返される可胜性のあるロヌカルメタ倉数を䜜成したした。ここ-同様に、すべおの倀のみがパブリックです

 //   : //     -   , //     negate (list list) = Cons(not_head, not_tail)' { h = list.head; //  not_head = Not(h); //   t = list.tail; //  not_tail = negate(t); //   } //     ! //   -    negate (Nil) = Nil; 

高階関数


単項関数ずリストのタむプを宣蚀し、マップを䜜成したこずを思い出しおください。

 //    map (unary f, list list) = Cons(new_head, new_tail)' { h = list.head; //  new_head = f(h); //   t = list.tail; //  new_tail = map(f, t); //   } //    map (unary f, Nil) = Nil; 

もちろん、unaryandの代わりに、それぞれをlistすぐに瀺すこずもできたすが、レコヌドは長くなり、芖芚的ではなくなりたす。val -> valval

短絡


クロヌゞャヌはC ++テンプレヌト自䜓によっおサポヌトされおいるため、ここで異垞なこずはありたせん。

 f(val x) = { g(val xs) = Cons(x, xs)'; } // : f(x)'.g(list)  map(f(x)'.g, list) 

ただし、返される関数の名前gなどを指定する必芁がありたす。返される名前のない関数には、名前valueずreturnが必芁valueです。C ++のクラスず同じ名前のメンバヌが既にコンストラクタヌに䞎えられおいるため、新しい意味を䞎えるこずでtypedefコンパむル゚ラヌが発生したす。

 template <typename x> struct f { template <typename xs> struct value { // value! typedef Cons<x, xs> value; // value! }; }; 

C ++では、ネストされたテンプレヌトに特化できないため、子メタ関数のパタヌンマッチングの代わりに、他のメカニズムを䜿甚する必芁がありたす。

C ++では、テンプレヌト内でテンプレヌトパラメヌタ名を再利甚するこずもできたせん。私の蚀語のこれらの名前はロヌカルメタ倉数を参照し、テンプレヌトを超えないため、名前の倉曎を自動化したした。

 f (val x) = g(Not(x)) { g (val x) = x; //    g (x1) = x1 // .. x   template f    x   template g } 

私にずっおは、このような倉換は「スポヌツマンらしさ」を远加したせんでした手䜜業による些现な亀換の可胜性があるため。


ある段階で、玔粋に機胜する郚分が倚かれ少なかれC ++に正垞に倉換され、少なくずも結果を出力できる必芁があるこずが刀明したした。この郚分は、私にずっお興味深いテンプレヌトのクリヌンな䞖界の範囲を超えおおりそのため、結果ずしおさらに悪いず考えられおいたした、さらに重芁なこずずしお、通垞のC ++で十分に説明されたした。テンプレヌトでコンパむル時コンピュヌティングを䜿甚するプログラムでは、テンプレヌトのない通垞のC ++が倖郚ぞのアクセスを担圓したす。぀たり、自分の蚀語をC ++のスヌパヌセットにするか、C ++の独自のアナログを䜜成する必芁がありたした。もちろん、最初のオプションを遞択したした。

残ったのは、C ++コヌドを挿入し、満足しお手をこするためのタグを入力するこずでした。しかし、論理的な問題が発生したした゜ヌスコヌドでは::value、メタ関数を適甚するために䜿甚されるこずはどこにも蚘茉されおいたせん。f(x)-これf<x>::valueではなくcall<f, x>::result。この知識は翻蚳者の内郚に保存されおおり、プログラムでの䜿甚は抜象化を打ち砎りたす。

 f(val x) = One; f(Zero) = Zero; main { print<f<One>::value>(); //  ? } 

興味のない呜什型郚分に関するアむデアはほずんどありたせんでした。いく぀かのオプションを考え出した埌、a通垞のC ++を䜿甚する可胜性のある呜什型ブロックを導入し、b玔粋な関数䞖界から倀を゚クスポヌトするこずにしたした。impure { }倀/関数を宣蚀する代わりにブロックが衚瀺され、関数を宣蚀するずきに「=」蚘号の盎埌に衚瀺される堎合がありたす。これらの堎合、C ++コヌドはプログラムの適切な堎所に挿入されたす。匏を゚クスポヌトするには、キヌワヌドをその前に配眮しimpureたす。C ++の芳点から芋るず、これは匏で蚘述されたタむプのオブゞェクトを䜜成するこずず同等です。

䜜業のデモンストレヌションずしおimpure、リストの「プリンタヌ」を準備したす。

 print(val list) { head = list.head; tail = list.tail; impure { //  typedef- head, tail    : print() { impure print(head); //   "print<head>();" std::cout << ", "; impure print(tail); } } } print(Zero) = impure { //  print(Zero) { impure { ...,   print() { std::cout << "0"; } } print(One) = impure { print() { std::cout << "1"; } } print(Nil) = impure { print() { std::cout << std::endl; } } 

名前空間


理論的には私の蚀語は、その䞭に、埓来のCで䜿甚されるラむブラリ++、I「prokinul」を䜜成するこずができたすのでnamespace、ずusing namespace

 namespace ns1 { namespace ns2 { f(val x) = x; } } namespace ns3 { using namespace ns1.ns2; g(val x) = f(x); } 

using namespaceたた、必芁な措眮。C ++では、堎合によっおは、ビュヌレコヌドでは十分ではありたせんf<x>::y<z>。あなたの堎合f、xたたはz-ではない特定のタむプ/テンプレヌト、およびテンプレヌトパラメヌタには、::yブラックボックスの出力ずなりたす。蚈算時に埗られるものを瀺す必芁がありたす::y-タむプたたはパタヌンtypename f<x>::yたたはなどf<x>::template y<z>。私はこれらの呜什を自動化せず、より単玔な手動蚘述のために構文糖を実装しなかったため、ドットを䜿甚するたびに「typename」のように芋えたす。すなわち f<x>::y<z>は間違った皮類のものに翻蚳されたすtypename typename f<x>::value::typename y<z>::value。名前空間の堎合、これは䞍芁でusing namespaceあり、typename挿入の必芁性を回避したす。

ラムダス


ラムダ関数なしで関数型蚀語を残したくありたせんでした。完党なサポヌトを実装するこずはできたせんでしたが、代わりに、少なくずも関数を宣蚀するずきに匕数を「=」蚘号の反察偎に転送するこずは可胜です

 f (val x) = y(x) { y(x) = g(x); } //  f = lambda (val x) -> y(x) { y(x) = g(x); } //  

ラムダの完党なサポヌトは、C ++ではa匿名クラスを䜜成できず、bタむプず同等になるたで説明を展開せずにテンプレヌトに盞圓するものを宣蚀できないずいう事実によっお劚げられたす。぀たり、数孊的には、x=y あなたは曞くこずができたすが、代わりに g=f䜿甚する必芁がありたす g(x)=f(x)。 呜什型プログラミングのナヌザヌはすでにこの状況に慣れおいたすが、関数型プログラミングの暙準ではこれはかなり面倒です。

 template <typename x> struct f {}; //    f using g = f; //      //    ,   template <typename x> using g = f<x>; // g<x> - , f<x> -  

ビュヌ゚ントリを実装map(lambda(val x) -> y, xs)するには、蚀語を倉曎し、䞀時的な名前でテンプレヌトを生成する必芁がありたす。

前述のように、value::valueこれはコンストラクタであるため、ラムダを返すラムダを盎接実装するこずはできたせん。ラムダから関数を返し、ビュヌレコヌドを蚱可g = fするには、他の抂念を䜿甚し、トランスレヌタヌをほが完党に曞き盎す必芁がありたす。

欠点ず可胜な解決策


  1. ロヌカル倉数の非衚瀺はありたせん。 コヌドに
    挿入impure { private: }するか、蚀語の基本的な倉曎を行うこずにより、単玔に解決されたす。
  2. «typename».
    , . (, fMap(fCurry(fCons).fApply(vXs), vObj.vYs) — template , typename , «f», «v» «n» ).

    — « ». - :

     //   x struct x { typedef internal::value type; //   x }; //   f(x) = x struct f { typedef internal::function type; //   f template <typename x> struct value { typedef typename x::type type; //    f struct result { //   ,   value::value typedef x value; }; }; }; 

    internal ; struct result value::value . - , ( , ), ( typedef ), , .
  3. : .
    .

    value , . , value::value value1::value2 :

     template <typename x> struct f { typedef internal::True uses_value1; //  value1 template <typename y> struct value1 { typedef internal::False uses_value1; //  value2 typedef Cons<x, y> value2; }; }; 

    , uses_value1 , value1 value2 .
  4. パタヌンパラメヌタずしおの可倉長パタヌンず敎数は考慮されたせん。
    実装のサポヌトに加えお、必芁なval远加の番号の皮類のint、uint、...ず配列[val]、[int]、[uint]...。メタフィヌルドを䜿甚する堎合、templateずtypenameで䞊蚘の問題を解決する必芁がありたす。数倀には䜕も必芁ありたせん;型ずパタヌンには必芁です。

プログラム䟋


䟋ずしおリストを䜜成したしょう {1,1,0}、2぀の方法で吊定を取り、これらすべおの倀を以䞋に出力しmainたす。

 //  : my_list = Cons(One, Cons(One, Cons(Zero, Nil)')')'; negated_list = negate(my_list); negated_list2 = map(Not, my_list); impure { int main() { //  : std::cout << "my list is "; impure print(my_list); std::cout << "negated list is "; impure print(negated_list); std::cout << "negated list is also "; impure print(negated_list2); } } 

プログラムは次を出力したす。

 my list is 1, 1, 0, negated list is 0, 0, 1, negated list is also 0, 0, 1, 

プログラムの゜ヌスコヌド党䜓
 impure { #include <iostream> } One; Zero; Not (val x) = Zero; //   Not (Zero) = One; //   And(val x, val y); //  x,y != One,Zero And   And(val x, One) = x; // One - , val x -  And(val x, Zero) = Zero; #type number = val; #type list = val; #type unary = number -> number; #type map_t = (unary, list) -> list; Nil; Cons(val x, val y) { head = x; tail = y; } //   : negate (list list) = Cons(not_head, not_tail)' { h = list.head; not_head = Not(h); t = list.tail; not_tail = negate(t); } //   -    negate (Nil) = Nil; //    map (unary f, list list) = Cons(new_head, new_tail)' { h = list.head; new_head = f(h); t = list.tail; new_tail = map(f, t); } //    map (unary f, Nil) = Nil; print(val list) { head = list.head; tail = list.tail; impure { print() { impure print(head); std::cout << ", "; impure print(tail); } } } print(Zero) = impure { print() { std::cout << "0"; } } print(One) = impure { print() { std::cout << "1"; } } print(Nil) = impure { print() { std::cout << std::endl; } } my_list = Cons(One, Cons(One, Cons(Zero, Nil)')')'; negated_list = negate(my_list); negated_list2 = map(Not, my_list); impure { int main() { std::cout << "my list is "; impure print(my_list); std::cout << "negated list is "; impure print(negated_list); std::cout << "negated list is also "; impure print(negated_list2); } } 


C ++での翻蚳埌のコヌド
 #include <iostream> struct One; struct Zero; template <typename x> struct Not { typedef Zero _value; }; template <> struct Not<Zero> { typedef One _value; }; template <typename x, typename y> struct And; template <typename x> struct And<x, One> { typedef x _value; }; template <typename x> struct And<x, Zero> { typedef Zero _value; }; struct Nil; template <typename x, typename y> struct Cons { typedef x head; typedef y tail; }; template <typename list> struct negate { typedef typename list::head h; typedef typename Not <h> ::_value not_head; typedef typename list::tail t; typedef typename negate <t> ::_value not_tail; typedef Cons <not_head, not_tail> _value; }; template <> struct negate<Nil> { typedef Nil _value; }; template <template <typename> class f, typename list> struct map { typedef typename list::head h; typedef typename f <h> ::_value new_head; typedef typename list::tail t; typedef typename map <f, t> ::_value new_tail; typedef Cons <new_head, new_tail> _value; }; template <template <typename> class f> struct map<f, Nil> { typedef Nil _value; }; template <typename list> struct print { typedef typename list::head head; typedef typename list::tail tail; print() { print <head> (); std::cout << ", "; print <tail> (); } }; template <> struct print<Zero> { print() { std::cout << "0"; } }; template <> struct print<One> { print() { std::cout << "1"; } }; template <> struct print<Nil> { print() { std::cout << std::endl; } }; typedef Cons <One, Cons <One, Cons <Zero, Nil> > > my_list; typedef typename negate <my_list> ::_value negated_list; typedef typename map <Not, my_list> ::_value negated_list2; int main() { std::cout << "my list is "; print <my_list> (); std::cout << "negated list is "; print <negated_list> (); std::cout << "negated list is also "; print <negated_list2> (); } 


翻蚳者


私はJavaScriptで翻蚳者を曞きたしたこの蚀語が倧奜きで、簡単に考えられたすパヌサヌゞェネレヌタヌずしおPEG.jsを䜿甚したした。

起動時に、トランスレヌタヌGitHub の蚀語ペヌゞを参照は、コマンドラむンパラメヌタヌずしお指定された名前のファむルを読み取り、結果のC ++プログラムテキストをstdoutに出力したす。

 node src/compile <> #  

翻蚳者が倚かれ少なかれ獲埗し、䜜業プログラムが曞かれるずすぐに、私はこのすべおをGitHubに投皿し、ゞョヌクの数孊者のように、「解決策がありたす」ず叫び、私はプロゞェクトの開発にほずんど興味を倱いたした。私は䞊蚘の問題をタむプ/ハンガリヌの衚蚘法を亀互に瀺すこずで解決しようずしたしたが、これには深刻な倉化ず繰り返される粟神的ストレスが必芁でした。考えお、準備ができお機胜しおいるものを持っおいるのは怠でした。さらに、最倧の適合性の原則に違反する可胜性がありたす。远加のラッパヌずそれらを䜿甚するためのコヌドは、攟送の単玔さの矎しさを殺し、テンプレヌトのネストの制限を超えおプログラムを近づけたす。

したがっお、私は栄誉にかかっおおり、誰かが興味を持ち、おそらく蚀語の開発に貢献しおくれたら嬉しいです。

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


All Articles