JavaScriptでプログラミング蚀語を実装する方法。 パヌト3CPSむンタヌプリタヌ

こんにちは JavaScriptプログラミング蚀語を実装するためのガむドの翻蚳の第3郚であるPLチュヌトリアルを玹介したす。


翻蚳者から


独自のプログラミング蚀語-λ蚀語 元の蚀語 -λanguageを䜜成したす。 䜜成のプロセスでは、再垰降䞋、コントロヌル転送スタむル、基本的な最適化手法など、倚くの興味深い手法を䜿甚したす。 むンタプリタの2぀のバヌゞョンが䜜成されたす-通垞のむンタプリタずCPSむンタプリタ、JavaScriptのトランスコンパむラ。


オリゞナルの䜜者は、有名なUglifyJSラむブラリJSコヌドを最小化およびフォヌマットするためのツヌルの䜜者であるMihai Bazonです。



PSむンタプリタずコンパむラにバグがありたす a() && b()たたはa() || b()ような匏で a() || b()䞡方の郚分が垞に実行されたす。 もちろん、これは間違っおa() &&挔算子に察しおa()停であるか、 ||に察しお停ではないためです。 、 b()倀は䜕の圹割も果たしたせん。 これを修正するのは難しくありたせん。


CPSむンタヌプリタヌ


λ蚀語には2぀の欠点がありたす。



ここで、むンタプリタがさらに遅くなるずいう事実に泚意を払うこずなく、最初の欠陥を修正したす。 むンタプリタを「継続枡しスタむル」CPSスタむルに曞き換えたす。


「継続転送」ずは


これは垞にNodeJSで行いたす


 fs.readFile("file.txt", "utf8", function CC(error, data){ //   - "" //     'return', // 'readFile'  ,  . }); 

すべおのステップで、続行する必芁があるずきに呌び出されるコヌルバックがありたす。 継続転送スタむルにより、コントロヌル転送が「明瀺的」になりたすreturn 、 throw 、 breakたたはcontinueは䜿甚したせん。 コヌドには暗黙のゞャンプはありたせん。 forたたはwhileルヌプを非同期関数で䜿甚するこずもできたせん。 この堎合、なぜプログラミング蚀語でそれらが必芁なのですか


継続を送信するスタむルでコヌドを曞くのは難しく、間違いを犯しやすいですが、むンタヌプリタヌはそのスタむルでのみ曞き換えたす。


evaluate関数


evaluate関数は、匏AST、コンテキスト環境、および結果が返されたずきに呌び出される関数の3぀の匕数を受け取りたす。 コヌドは次のずおりです。各フラグメントに぀いお説明がありたす。


 function evaluate(exp, env, callback) { switch (exp.type) { 

定数に぀いおは、その倀を返すだけです。 ただし、 returnはありたせん。代わりに、コヌルバックを呌び出しお倀を枡すだけです。


  case "num": case "str": case "bool": callback(exp.value); return; 

varノヌドも単玔です-コンテキストから倉数を取埗し、関数に枡したす


  case "var": callback(env.get(exp.value)); return; 

assignノヌドの堎合、巊の匏の倀 right を取埗する必芁がありたす。 これを行うには、 evaluateを呌び出しお、結果を取埗する関数を枡したす匏の右偎。 そしお、倉数倀でコヌルcallbackを呌び出し、珟圚のコンテキストで倉数を蚭定したす。


  case "assign": if (exp.left.type != "var") throw new Error("Cannot assign to " + JSON.stringify(exp.left)); evaluate(exp.right, env, function(right){ callback(env.set(exp.left.value, right)); }); return; 

binaryノヌドでもほが同じですが、ここではたずleftフィヌルドの倀を取埗し、次にrightフィヌルドの倀を取埗する必芁がありたす。 次に、コヌルバックを呌び出しお、操䜜の結果を枡したす。


  case "binary": evaluate(exp.left, env, function(left){ evaluate(exp.right, env, function(right){ callback(apply_op(exp.operator, left, right)); }); }); return; 

letノヌドはより耇雑に芋えたすが、実際は単玔です。 いく぀かの倉数がありたす。 それらのdefフィヌルド初期倀は空かもしれたせん。その堎合、 false蚭定しfalse 。 ただし、倀がある堎合は、それを取埗するためにevaluate再垰的に呌び出す必芁がありたす。


NodeJSを以前に䜿甚したこずがある堎合は、すでにこれを䜕床も実行しおいたす。 コヌルバックのため、 for䜿甚するこずはできたせん。したがっお、これらの匏を䞀床に1぀ず぀解釈する必芁がありたす evaluate関数を非同期ず考えおください。 以䞋のloop関数すぐに呌び出されたすは、凊理する必芁のある定矩ずコンテキストを取埗したす



前ず同様に、ラムダノヌドは別の関数で凊理されたす。


  case "lambda": callback(make_lambda(env, exp)); return; 

ifを解釈するために、たず条件を解釈したす。 falseでない堎合、 then匏を解釈し、別のケヌスではelseあればそれを解釈し、ない堎合はfalse返しfalse 。 前ず同じように、 thenおよびelseに察しお、匏の「実行埌に実行するアクション」ずしおcallbackを枡すだけelse 。


  case "if": evaluate(exp.cond, env, function(cond){ if (cond !== false) evaluate(exp.then, env, callback); else if (exp.else) evaluate(exp.else, env, callback); else callback(false); }); return; 

progノヌドはletノヌドず同様に解釈されたすが、コンテキストをコピヌしたり倉数を定矩したりする必芁がないずいう違いがありたす。 たた、匏番号を受け取るloop関数がありたす。 prog.lengthに等しい堎合、すべおの匏が完成し、最埌の匏の結果が返されたす「return」ずいう蚀葉で、コヌルcallbackを呌び出しcallback 。 最埌の倀をloop関数ぞの匕数ずしお枡すこずで芚えおいるこずに泚意しおください最初は本䜓が空の堎合はfalseです


  case "prog": (function loop(last, i){ if (i < exp.prog.length) evaluate(exp.prog[i], env, function(val){ loop(val, i + 1); }); else { callback(last); } })(false, 0); return; 

型callノヌドのfunc 、 funcを解釈する必芁があり、すべおの匕数が順番に䞊んでいたす。 たた、 letたたはprogず同じ原理で動䜜するloop関数がありloop 、結果ずしお配列を䜜成するずいう違いがありたす


  case "call": evaluate(exp.func, env, function(func){ (function loop(args, i){ if (i < exp.args.length) evaluate(exp.args[i], env, function(arg){ args[i + 1] = arg; loop(args, i + 1); }); else { func.apply(null, args); } })([ callback ], 0); }); return; 

さお、暙準的な゚ンディング䜕をすべきかわからない堎合は、䟋倖をスロヌしたす


  default: throw new Error("I don't know how to evaluate " + exp.type); } } 

䞊蚘の各caseがreturnキヌワヌドで終わるこずに気付くかもしれたせん。 ただし、戻り倀はありたせん。結果は垞にcallback関数に枡されcallback 。


新しいmake_lambda関数


このむンタヌプリタヌでは、すべおの関数は最初の匕数ずしお「継続」を受け取りたす。結果を枡すために呌び出す必芁のある関数です。 その埌は通垞の関数匕数です。 make_lambda関数の新しいコヌドは次のmake_lambdaです。


 function make_lambda(env, exp) { if (exp.name) { env = env.extend(); env.def(exp.name, lambda); } function lambda(callback) { var names = exp.vars; var scope = env.extend(); for (var i = 0; i < names.length; ++i) scope.def(names[i], i + 1 < arguments.length ? arguments[i + 1] : false); evaluate(exp.body, scope, callback); } return lambda; } 

圌はそれほど違いはありたせん。 新しいコンテキストに新しい倉数を远加したす。 たた、最初の匕数がcallbackであるこずを考慮する必芁がありcallback 。 最埌に、 evaluate関数は新しいコンテキストで関数コヌドを実行するために䜿甚evaluateたすが、前述のように、 callbackを枡しcallback 。


ちなみに、これはforルヌプを䜿甚した唯䞀の堎所です。 これは、 callノヌドが凊理されたずきに匕数倀がすでに蚈算されおいるためです。


ネむティブ関数


このむンタヌプリタヌでは、ネむティブ関数は最初の匕数ずしおcallbackを受け取りたす。 ネむティブ関数を定矩するずき、これを芚えおおく必芁がありたす。 新しいむンタヌプリタヌのサンプルコヌドを次に瀺したす。


 var code = "sum = lambda(x, y) x + y; print(sum(2, 3));"; var ast = parse(TokenStream(InputStream(code))); var globalEnv = new Environment(); // define the "print" primitive function globalEnv.def("print", function(callback, txt){ console.log(txt); callback(false); // call the continuation with some return value // if we don't call it, the program would stop // abruptly after a print! }); // run the evaluator evaluate(ast, globalEnv, function(result){ // the result of the entire program is now in "result" }); 

少しテスト


フィボナッチ数をもう䞀床蚈算しおみたしょう。


 fib = λ(n) if n < 2 then n else fib(n - 1) + fib(n - 2); time( λ() println(fib(10)) ); 

しかし、27番目の数倀を芋぀けようずするず、スタックオヌバヌフロヌが発生したす。 䞀般に、スタックは今よりずっず速く成長しおいるので、フィボナッチ数をカりントできるのは12日たでです少なくずも私のブラりザヌでは。 これはあたり良くないので、どうにか修正する必芁がありたす。


スタックを保護したす


CPSむンタヌプリタヌでは、むンタヌプリタヌは垞に関数を再垰的に呌び出し、結果を返さないため、スタックは非垞に速く成長したす。 むンタプリタにreturnたすが、それらは必芁ですが、非垞に深い再垰の堎合、それらに到達するこずはありたせん。


スタックが非垞に単玔なプログラムをどのように芋えるか想像しおみたしょう。 擬䌌コヌドを衚瀺したすが、ここでは䜕の圹割も果たさないため、 env远加したせんでした。


 print(1 + 2 * 3); ## : evaluate( print(1 + 2 * 3), K0 ) evaluate( print, K1 ) K1(print) #  'var',      evaluate( 1 + 2 * 3, K2 ) evaluate( 2 * 3, K3 ) evaluate( 2, K4 ) K4(2) # 2  ,      evaluate( 3, K5 ) #    3,      K5(3) K3(6) #  2*3 evaluate( 1, K6 ) #  ,  K6(1) K2(7) #  1+2*3 print(K0, 7) # ,     'print' K0(false) #  . 'print'  'false'. 

最埌の呌び出しの埌のみ、無駄なreturn長いシヌケンスはスタックを枛らしたす。 単玔なプログラムに非垞に倚くのスタック領域を䜿甚する堎合、 fib(13)䜕が起こるか想像するのは困難です。


スタック保護


新しいむンタヌプリタヌでは、スタックは必芁ありたせん。 䜕らかの匏が匕数ずしお枡されるcallbackで発生した埌に行う必芁があるすべお。 ここで質問がありたすJavaScriptがスタックを「ダンプ」するこずを可胜にしたらどうなるでしょうか。 その埌、スタックをドロップするず、無限に深い再垰が機胜したす。


これを行うこずができるGUARD関数があるず想像しおみたしょう。 2぀の倀を取埗したす。呌び出す関数ず、枡す必芁のある匕数です。 スタックが深すぎる堎合、スタックをクリアし、枡された関数を呌び出したす。 別のケヌスでは、圌女は䜕もしたせん。


新しい関数を䜿甚しお、以䞋に瀺すようにむンタヌプリタヌを曞き換えたす。 私は個々のケヌスに぀いおコメントしたせん。前に説明したコヌドがありたすが、 GUARD関数を䜿甚しおいたす。


 function evaluate(exp, env, callback) { GUARD(evaluate, arguments); switch (exp.type) { case "num": case "str": case "bool": callback(exp.value); return; case "var": callback(env.get(exp.value)); return; case "assign": if (exp.left.type != "var") throw new Error("Cannot assign to " + JSON.stringify(exp.left)); evaluate(exp.right, env, function CC(right){ GUARD(CC, arguments); callback(env.set(exp.left.value, right)); }); return; case "binary": evaluate(exp.left, env, function CC(left){ GUARD(CC, arguments); evaluate(exp.right, env, function CC(right){ GUARD(CC, arguments); callback(apply_op(exp.operator, left, right)); }); }); return; case "let": (function loop(env, i){ GUARD(loop, arguments); if (i < exp.vars.length) { var v = exp.vars[i]; if (v.def) evaluate(v.def, env, function CC(value){ GUARD(CC, arguments); var scope = env.extend(); scope.def(v.name, value); loop(scope, i + 1); }); else { var scope = env.extend(); scope.def(v.name, false); loop(scope, i + 1); } } else { evaluate(exp.body, env, callback); } })(env, 0); return; case "lambda": callback(make_lambda(env, exp)); return; case "if": evaluate(exp.cond, env, function CC(cond){ GUARD(CC, arguments); if (cond !== false) evaluate(exp.then, env, callback); else if (exp.else) evaluate(exp.else, env, callback); else callback(false); }); return; case "prog": (function loop(last, i){ GUARD(loop, arguments); if (i < exp.prog.length) evaluate(exp.prog[i], env, function CC(val){ GUARD(CC, arguments); loop(val, i + 1); }); else { callback(last); } })(false, 0); return; case "call": evaluate(exp.func, env, function CC(func){ GUARD(CC, arguments); (function loop(args, i){ GUARD(loop, arguments); if (i < exp.args.length) evaluate(exp.args[i], env, function CC(arg){ GUARD(CC, arguments); args[i + 1] = arg; loop(args, i + 1); }); else { func.apply(null, args); } })([ callback ], 0); }); return; default: throw new Error("I don't know how to evaluate " + exp.type); } } 

無名関数の堎合、 GUARD関数に枡すこずができるように名前を远加する必芁がありたした。 私は名前CC  current continuation を䜿甚したした。 arguments.callee䜿甚できたすが、これは時代遅れのAPIです。


たた、 make_lambdaの同じ倉曎


 function make_lambda(env, exp) { if (exp.name) { env = env.extend(); env.def(exp.name, lambda); } function lambda(callback) { GUARD(lambda, arguments); // <--  var names = exp.vars; var scope = env.extend(); for (var i = 0; i < names.length; ++i) scope.def(names[i], i + 1 < arguments.length ? arguments[i + 1] : false); evaluate(exp.body, scope, callback); } return lambda; } 

GUARDの実装GUARD非垞に簡単です。 ディヌプコヌルから抜け出すには 䟋倖を䜿甚したす。 これを行うには、さらに倚くの再垰を実行できるこずを瀺すグロヌバル倉数がありたす。 れロに達した堎合、 ContinuationオブゞェクトをスロヌしたすContinuationオブゞェクトには、関数ず匕数の継続がありたす。


 var STACKLEN; function GUARD(f, args) { if (--STACKLEN < 0) throw new Continuation(f, args); } function Continuation(f, args) { this.f = f; this.args = args; } 

そしお最埌に、 Continuationオブゞェクトをキャッチするルヌプが必芁です。 すべおが機胜するように、このルヌプを介しおむンタヌプリタヌを呌び出す必芁がありたす。


 function Execute(f, args) { while (true) try { STACKLEN = 200; return f.apply(null, args); } catch(ex) { if (ex instanceof Continuation) f = ex.f, args = ex.args; else throw ex; } } 

Execute関数は、呌び出される関数ずその匕数を受け入れたす。 ルヌプ内で機胜したすが、関数がスタックオヌバヌフロヌなしで機胜する堎合は、すぐに停止するように泚意しおください。 STACKLEN 、ルヌプの反埩を開始するたびにリセットされたす。 倀200-よく合いたす。 Continuationオブゞェクトをキャッチするず、新しい関数ず匕数を眮き換えお、ルヌプを継続したす。 たた、䟋倖のため、スタックはクリアされ、続行できたす。


むンタヌプリタヌを開始するには、 Executeを䜿甚したす。


 Execute(evaluate, [ ast, globalEnv, function(result){ console.log("*** Result:", result); }]); 

テスト


fib関数が機胜するようになりたした


 fib = λ(n) if n < 2 then n else fib(n - 1) + fib(n - 2); time( λ() println(fib(20)) ); # 6765, ~300ms 

残念ですが、 fib(27)を芋぀けようずするず、通垞の通蚳の玄4倍の時間がかかりたす。 しかし、今では無限の再垰がありたす。


 sum = λ(n, ret) if n == 0 then ret else sum(n - 1, ret + n); # compute 1 + 2 + ... + 50000 time( λ() println(sum(50000, 0)) ); # 1250025000, ~700ms 

もちろん、λ蚀語はJavaScriptよりもはるかに遅いです。 想像しおみおください。倉数ぞのアクセスはすべお、 Environmentオブゞェクトを経由したす。 むンタヌプリタヌを最適化しようずしおも意味がありたせん。パフォヌマンスが倧幅に向䞊するこずはありたせん。 パフォヌマンスを改善するための1぀の解決策がありたす。λ蚀語をJSでコンパむルしたす。これが私たちがやるこずです。 最初に、CPSむンタヌプリタヌで䜕ができるか芋おみたしょう。


継続


むンタヌプリタヌは継続を送信するスタむルで動䜜し、すべおの関数λ蚀語関数ずネむティブJS関数の䞡方は、結果を返す最初の匕数ずしお継続関数を受け取りたすこの匕数は、λ蚀語関数には衚瀺されたせんが、JavaScript関数に必芁です。


倉数callbackは、プログラム党䜓の継続を意味しcallback 。 プログラムが次に行うすべおのこず。 この倉数を「珟圚の継続」たたはコヌド内のkず呌びたす。


たた、継続を行わない堎合、プログラムは停止したす。 ずにかくmake_lambdaは継続を呌び出すため、λ蚀語からこれを行うこずはできたせん。 しかし、ネむティブ関数からこれを行うこずができたす。 どのように機胜するかを瀺す関数を䜜成したした。


 globalEnv.def("halt", function(k){}); 

これは䜕もしない関数です。 圌女は継続 k を受け取りたすが、それを呌び出したせん


 println("foo"); halt(); println("bar"); 

結論


 foo 

halt()呌び出しを削陀するず、出力は次のようになりたす foo / bar / ***Result: false 最埌のprintlnがfalse返すため。 ただし、 halt() fooのみが出力されたす。 * halt()は継続を匕き起こさないため、結果もありたせん。したがっお、 evaluateために枡したコヌルバック-文字列***Resultを衚瀺するコヌルバックは呌び出されたせん。 evaluateを呌び出した関数は、プログラムが停止したこずに気付きたせん。 NodeJSでは、それは足元でのショットになりたす。




ブラりザを停止せずにしたがっお、愚かなルヌプなしでプログラムを停止するsleepe関数が必芁だず想像しおください。 ネむティブ関数を䜿甚しおこれを簡単に実装できたす。


 globalEnv.def("sleep", function(k, milliseconds){ setTimeout(function(){ Execute(k, [ false ]); //   ,  'false' }, milliseconds); }); 

少し䞍䟿なのは、 setTimeoutがコヌルバックを匕き起こし、それがContinuationをスロヌし、誰もキャッチしないため、 Executeを䜿甚する必芁があるこずです。 䜿甚方法は次のずおりです。


 let loop (n = 0) { if n < 10 { println(n); sleep(250); loop(n + 1); } }; println("And we're done"); 

結論


 0 1 2 3 4 5 6 7 8 9 And we're done ***Result: false 

各ラむン間にわずかな遅延があるこずに泚意しおください。 元の蚘事でこのコヌドを実行しおみおください 。


継続の手動送信を䜿甚するこずを陀いお、これはすでに通垞のJavaScriptでは実行できないこずですたた、 forルヌプfor䜿甚するこずもできたせん。


 (function loop(n){ if (n < 10) { console.log(n); setTimeout(function(){ loop(n + 1); }, 250); } else { println("And we're done"); //    } })(0); 



NodeJS APIをλ蚀語で䜿甚する方法を想像しおください。


 globalEnv.def("readFile", function(k, filename){ fs.readFile(filename, function(err, data){ // error handling is a bit more complex, ignoring for now Execute(k, [ data ]); // hope it's clear why we need the Execute }); }); globalEnv.def("writeFile", function(k, filename, data){ fs.writeFile(filename, data, function(err){ Execute(k, [ false ]); }); }); 

䜿甚法


 copyFile = λ(source, dest) { writeFile(dest, readFile(source)); }; copyFile("foo.txt", "bar.txt"); 

そしお、これはすべお非同期に機胜したす。 これ以䞊のコヌルバック地獄。




より興味深い䟋を次に瀺したす。 次のネむティブ関数を䜜成したした。


 globalEnv.def("twice", function(k, a, b){ k(a); k(b); }); 

プログラムは、2぀の匕数aずbを受け取り、匕数ごずに1回、継続を2回呌び出したす。 それを呌び出しおみたしょう


 println(2 + twice(3, 4)); println("Done"); 

結論


 5 Done ***Result: false 6 Done ***Result: false 

これたでに継続の受け枡しに取り組んだこずがないが、このコヌドを自分で理解しようずする堎合、結論は奇劙です。 ちょっずしたヒントプログラムは1回起動したすが、結果は2回返されたす。


汎化 CallCC


ネむティブ関数を曞くずき、私たちはか぀お火で遊んでいたした。 λ蚀語では、珟圚の継続の実行を䞭断する方法はありたせん。 CallCCはこの問題の解決に圹立ちたす。 名前は、Schemeの関数の省略圢です-call call-with-current-continuation 通垞、Schemeではcall / ccず呌ばれたす。


 globalEnv.def("CallCC", function(k, f){ f(k, function CC(discarded, ret){ k(ret); }); }); 

珟圚の継続を、λ蚀語から盎接呌び出すこずができる関数に具䜓化したす。 この関数は、元のdiscarded継続を無芖し、代わりにCallCC関数に枡された継続を呌び出したす。


この関数を䜿甚しお、ネむティブ関数ではなく、すでにλ蚀語で䟋倖から開始しおreturnで終了するreturnたで考えもしなかった実行フロヌ制埡挔算子の倧きなセットを実装できたす。 最埌から始めたしょう。


実装return


 foo = λ(return){ println("foo"); return("DONE"); println("bar"); }; CallCC(foo); 

結論


 foo ***Result: DONE 

foo関数は、JavaScriptからのreturnず同じ匕数を受け取りたすただし、通垞の関数ずしお呌び出されたす。 珟圚の継続 'bar'を出力するをスキップし、関数を終了しお、指定された倀 "DONE"を返したす。 もちろん、これはCallCCを䜿甚しお関数を呌び出し、継続をreturnずしお枡す堎合にのみ機胜したす。 これのラッパヌを䜜成できたす。


 with-return = λ(f) λ() CallCC(f); foo = with-return(λ(return){ println("foo"); return("DONE"); println("bar"); }); foo(); 

結論


 foo ***Result: DONE 

このメ゜ッドの利点は、 fooを呌び出すずきにCallCCを䜿甚する必芁がなくなるこずです。 もちろん、匕数ずしおreturnを受け入れるか、 with-return関数を䜿甚する必芁がない堎合は良いでしょうが、蚀語では構文シュガヌを盎接远加する方法はありたせん。このため、少なくずもパヌサヌを倉曎する必芁がありたすLispの堎合は+1。


遷移


たずえば、乗算が84になる堎合、100たでの正の敎数のすべおのペアを怜玢するプログラムを䜜成する必芁がありたす。これは難しい䜜業ではなく、すぐに解決する2぀のネストされたルヌプを想像できたすが、ここでは別の方法を説明したす。 guessずfail 2぀の関数を䜜成したす。 最初の人は番号を遞び、2番目の人は圌女に「別の番号を詊しおください」ず䌝えたす。 次のように䜿甚したす。


 a = guess(1); #  -  >= 1 b = guess(a); #  -  >= a if a * b == 84 { #    : print(a); print(" x "); println(b); }; fail(); #    `guess`     

:


 fail = λ() false; guess = λ(current) { CallCC(λ(k){ let (prevFail = fail) { fail = λ(){ current = current + 1; if current > 100 { fail = prevFail; fail(); } else { k(current); }; }; k(current); }; }); }; a = guess(1); b = guess(a); if a * b == 84 { print(a); print(" x "); println(b); }; fail(); 

, , a , 84 , b , 84 / a . guess : start end — . , .


try catch Common Lisp


— catch throw . :


 f1 = λ() { throw("foo", "EXIT"); print("not reached"); }; println(catch("foo", λ() { f1(); print("not reached"); })); #  EXIT 


:


 ##  , 'throw'   . ##       `error`, ##     JavaScript.    . throw = λ(){ println("ERROR: No more catch handlers!"); halt(); }; catch = λ(tag, func){ CallCC(λ(k){ let (rethrow = throw, ret) { ##   ,     . throw = λ(t, val) { throw = rethrow; #   ,   . if t == tag then k(val) else throw(t, val); }; ##      . ret = func(); ##       (  'throw') ##    .   . throw = rethrow; # XXX ##  . ret; }; }); }; 

䟋


 # ... f1 = λ() { throw("foo", "EXIT"); print("not reached"); }; println(catch("foo", λ() { f1(); print("not reached"); })); 


catch , , , . , , , CallCC . , . " " — — . , , , catch / throw , .


. , catch . , throw , , catch , . 䟋


 exit = false; #  . x = 0; # :   ,   'exit()'  CallCC( λ(k) exit = k ); ## 'exit()'   ... if x == 0 then catch("foo", λ(){ println("in catch"); x = 1; #  exit(); }); println("After catch"); throw("foo", "FOO"); 

結論


 After catch After catch ERROR: No more catch handlers! 

'After catch' , 'ERROR: No more catch handlers!'. - , 'After catch' , . , '' , catch . , 'XXX' catch , throw , catch .


( , .)


CallCC (, , CallCC ). , — CallCC .


Yield


, , yield :


 foo = with-yield(λ(yield){ yield(1); yield(2); yield(3); "DONE"; }); println(foo()); # 1 println(foo()); # 2 println(foo()); # 3 println(foo()); # DONE 

yield , . , return . , , yield , .


:


 fib = with-yield(λ(yield){ let loop (a = 1, b = 1) { yield(b); loop(b, a + b); }; }); ##   50   let loop (i = 0) { if i < 50 { println(fib()); loop(i + 1); }; }; 

fib . . yield . , , 50 , 50 .


, , , , .



, .


yield , , . , , return . , , yield , yield , . , . :


 with-yield = λ(func) { ## with-yield     λ() { CallCC(λ(kret){ # kret     let (yield) { ##  yield yield = λ(value) { # yield  ,    CallCC(λ(kyld){ # kyld    yield... func = kyld; # ...     kret(value); #  . }); }; ## , ,  ,   yield. func(yield); }; }); }; }; 

yield , . , , , "DONE".


, . , - , , 4 :


 println(foo()); foo(); 

.


№1: yield


, , , , yield ( kyld , , ) :


  yield(2); yield(3); "DONE"; 

yield ? yield , , yield . , . , yield return , :


 with-yield = λ(func) { let (return, yield) { yield = λ(value) { CallCC(λ(kyld){ func = kyld; return(value); # 'return'  ,     }); #        . }; # λ(val) { # CallCC(λ(kret){ # return = kret; # <-  func(val || yield); }); }; }; }; 

, , yield , yield ( ). yield .


, , println(foo()) :


 with-yield = λ(func) { let (return, yield) { yield = λ(value) { CallCC(λ(kyld){ func = kyld; return(value); }); }; λ(val) { CallCC(λ(kret){ return = kret; func(val || yield); }); }; }; }; foo = with-yield(λ(yield){ yield(1); yield(2); yield(3); "DONE"; }); println(foo()); println(foo()); println(foo()); 

, . , print(foo()); foo() . , println(foo()) ? ...


№2: WTF?


. : , foo() . , ? — .


 println(foo()); ## yield 1 <-----------------  ---------------------------+ println(foo()); ## yield 2 | println(foo()); ## yield 3 | println(foo()); ##   "DONE",   foo()  -->--+ 

with-yield :


  func(val || yield); #... 

yield , , #... . , , ( "DONE" ), , "DONE" , . foo() , "DONE" . :


 println(foo()); println("bar"); println(foo()); println(foo()); foo(); 

: 1, bar, 2, 3, DONE, bar, DONE, bar, ... .


func - , . , "no more continuations" :


  val = func(val || yield); func = λ() "NO MORE CONTINUATIONS"; kret(val); 

:


 with-yield = λ(func) { let (return, yield) { yield = λ(value) { CallCC(λ(kyld){ func = kyld; return(value); }); }; λ(val) { CallCC(λ(kret){ return = kret; val = func(val || yield); func = λ() "NO MORE CONTINUATIONS"; kret(val); }); }; }; }; foo = with-yield(λ(yield){ yield(1); yield(2); yield(3); "DONE"; }); println(foo()); println(foo()); println(foo()); println(foo()); 

, :


 1 2 3 DONE NO MORE CONTINUATIONS NO MORE CONTINUATIONS NO MORE CONTINUATIONS ***Result: false 

1, 2, 3, DONE , "NO MORE CONTINUATIONS" . , :


 print("A. "); println(foo()); print("B. "); println(foo()); print("C. "); println(foo()); print("D. "); println(foo()); ##   : A. 1 B. 2 C. 3 D. DONE B. NO MORE CONTINUATIONS C. NO MORE CONTINUATIONS D. NO MORE CONTINUATIONS ***Result: false 

, : , , , , "B." .


, yield , :


 with-yield = λ(func) { let (return, yield) { yield = λ(value) { CallCC(λ(kyld){ func = kyld; return(value); }); }; λ(val) { CallCC(λ(kret){ return = kret; val = func(val || yield); func = λ() "NO MORE CONTINUATIONS"; kret(val); }); }; }; }; fib = with-yield(λ(yield){ let loop (a = 1, b = 1) { yield(b); loop(b, a + b); }; }); ##   50   let loop (i = 0) { if i < 50 { println(fib()); loop(i + 1); }; }; 

おわりに
 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 2971215073 4807526976 7778742049 12586269025 20365011074 ***Result: false 

, (, ), " ".


: reset shift


yield : reset shift . " " — , . reset , shift , CallCC .


, reset shift — . reset , shift , reset .


, with-yield :


 with-yield = λ(func) { let (yield) { ## 'yield'  'shift'     ##  'reset'.  ,   ,    ##   'func' — ,   `func()` ##    ,    . yield = λ(val) { shift(λ(k){ func = k; #    val; #    }); }; ##  `with-yield`      ##   'reset',    ,  ##   'yield' ( )    ##    λ(val) { reset( λ() func(val || yield) ); }; } }; 

, reset . , , , reset . , . , .


:


 with-yield = λ(func) { let (yield) { yield = λ(val) { shift(λ(k){ func = k; val; }); }; λ(val) { reset( λ() func(val || yield) ); }; } }; foo = with-yield(λ(yield){ yield(1); yield(2); yield(3); "DONE"; }); println(foo()); #  1 println(foo()); #  2 println(foo()); #  3 println(foo()); #  DONE 

reset shift


, . . . , , , . Scheme ( — Oleg Kiselyov ). .



, ( CallCC ) . :


 var pstack = []; function _goto(f) { f(function KGOTO(r){ var h = pstack.pop(); h(r); }); } globalEnv.def("reset", function(KRESET, th){ pstack.push(KRESET); _goto(th); }); globalEnv.def("shift", function(KSHIFT, f){ _goto(function(KGOTO){ f(KGOTO, function SK(k1, v){ pstack.push(k1); KSHIFT(v); }); }); }); 

, reset , shift _goto , — . . _goto ( KGOTO ). , f ( CallCC ) - KGOTO , . , f , , KGOTO , , .


reset ( KRESET ) _goto(th) . , , , _goto . , , KGOTO KRESET .


, shift KGOTO , KGOTO pstack , SK , , shift ( shift — KSHIFT ). SK — — ( k1 ) . , shift2 , , pstack.push(k1); 


 println(reset(λ(){ 1 + shift( λ(k) k(k(2)) ); })); println(reset(λ(){ 1 + shift2( λ(k) k(k(2)) ); })); 

結論


 4 3 ***Result: false 

shift ( k ), reset . — 1 shift :


 1 + [?] 

k , ? . — k(k(2)) . 2 , k(2) 3. , k(3) 3 , 4.


shift2 : k(2) .


CallCC


, , CallCC , . , JS, , , . , CallCC , .


— goto , ( ):


 pstack = NIL; goto = false; reset = λ(th) { CallCC(λ(k){ pstack = cons(k, pstack); goto(th); }); }; shift = λ(f) { CallCC(λ(k){ goto(λ(){ f(λ(v){ CallCC(λ(k1){ pstack = cons(k1, pstack); k(v); }); }); }); }); }; let (v = CallCC( λ(k){ goto = k; k(false) } )) { if v then let (r = v(), h = car(pstack)) { pstack = cdr(pstack); h(r); } }; 

— !



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


All Articles