简体   繁体   English

转译器之战:突破嵌套 function,有 vs 无 throw

[英]transpiler battle: breaking out of nested function, with vs without throw

I have just finished writing "version 0" of my first (toy) transpiler.我刚刚完成了我的第一个(玩具)转译器的“版本 0”。 It works.有用。 It turns a string of "pseudo JavaScript" (JavaScript with an additional feature) into a string of runnable JavaScript.它将一串“伪 JavaScript”(带有附加功能的 JavaScript)转换为一串可运行的 JavaScript。 Now, I want to improve it.现在,我想改进它。

The work area possibly most interesting for other SO users is this: The compiled code (ie, output of my transpiler) does not heed a coding style recommendation as given in an accepted answer to some earlier SO question.其他 SO 用户可能最感兴趣的工作区域是:编译后的代码(即,我的转译器的 output)没有注意到前面一些 SO 问题的已接受答案中给出的编码风格建议。 If I would have at my hands a second transpiler where that coding style recommendation is heeded, I could make an informed decision regarding which branch is more promising to continue to develop on - I'd like to compare the 2 braches regarding performance, development time needed, amount of bugs, and so on, and decide based on that.如果我手头有第二个转译器,该编码风格建议得到重视,我可以就哪个分支更有希望继续开发做出明智的决定——我想比较这两个分支的性能、开发时间需要,错误的数量等,并据此决定。

Let me tell you about the "additional JS feature" my transpiler deals with: "nested return".让我告诉你我的转译器处理的“附加 JS 功能”:“嵌套返回”。 Consider closures / nested functions like so考虑像这样的闭包/嵌套函数

function myOuterFunc(){
    ... code ...
    function innerFunc(){
        ... code ...
    }
    ... code ...
}

(note that above '...code...' is supposed to include every possible JS code including more nested function declarations, so myOuterFunc is not necessarily the direct parent of innerFunc ) (请注意,上面的 '...code...' 应该包括所有可能的 JS 代码,包括更多嵌套的 function 声明,因此myOuterFunc不一定是innerFunc的直接父级)

In above situation, suppose you desire to return a result from myOuterFunc from somewhere inside - not necessarily directly inside - innerFunc在上述情况下,假设您希望从内部某处返回myOuterFunc的结果 - 不一定直接在内部 - innerFunc

With "nested return" implemented, you could then write simply实现“嵌套返回”后,您可以简单地编写

return.myOuterFunc result

Here is an exmalpe of a (not-runnable) function using this feature and doing something meaningful这是(不可运行的)function 使用此功能并做一些有意义的事情的示例

function multiDimensionalFind(isNeedle, haystack) {
    // haystack is an array of arrays
    // loop (recursively) through all ways of picking one element from each array in haystack
    // feed the picked elements as array to isNeedle and return immediately when isNeedle gives true
    // with those picked elements being the result, i.e. the 'found needle'
    var LEVEL = haystack.length;
    function inner(stack) {
        var level = stack.length;
        if (level >= LEVEL) {
            if (isNeedle(stack)) return.multiDimensionalFind stack;
        } else {
            var arr = haystack[level];
            for (var i = 0; i < arr.length; i++) {
                inner(stack.concat([arr[i]]));
            }
        }
    }
    inner([]);
    return 'not found'
}

And here is the (runnable) code my transpiler automatically produces from that (comments were added/remove later, obviously), followed by some code testing if that function does what it claims to do (and it does, as you can convince yourself.)这是我的转译器自动生成的(可运行的)代码(很明显,注释是后来添加/删除的),然后是一些代码测试,如果 function 做了它声称做的事情(它确实如此,因为你可以说服自己。 )

 ///////////// the function ///////////////// function multiDimensionalFind(isNeedle, haystack) { try { var LEVEL = haystack.length; function inner(stack) { var level = stack.length; if (level >= LEVEL) { if (isNeedle(stack)) throw stack; } else { var arr = haystack[level]; for (var i = 0; i < arr.length; i++) { inner(stack.concat([arr[i]])); } } } inner([]); return 'not found' } catch(e){ // make sure "genuine" errors don't get destroyed or mishandled if (e instanceof Error) throw e; else return e; } } ////////////////// test it ////////////////// content = document.getElementById('content'); function log2console(){ var digits = [0,1]; var haystack = [digits,digits,digits,digits,digits]; var str = ''; function isNeedle(stack){ str = str + ', ' + stack.join('') return false; } multiDimensionalFind(isNeedle, haystack); content.textContent = str; } function find71529(){ // second button var digits = [0,1,2,3,4,5,6,7,8,9] var haystack = [digits,digits,digits,digits,digits] function isNeedle(stack){ return stack.reduce(function(b,i){ return 10*b+i; }, 0) === 71529 // returns true iff the stack contains [7,1,5,2,9] } content.textContent = multiDimensionalFind( isNeedle, haystack ).join('_') }
 <button onclick='log2console()'>print binary numbers with 5 digits</button> <br> <button onclick='find71529()'>find something is 5d space</button> <div id='content'></div>

You can play around with my transpiler in this fiddle here .你可以在这里玩我的这个小提琴中的转译器。 I'm using the esprima library , the escodegen.js library on top of esprima, a teeny tiny work in progress abstract syntax tree generation library of my own (see the script tags in the fiddle).我正在使用esprima 库,即esprima之上的 escodegen.js 库,这是我自己的一个正在进行中的微小的抽象语法树生成库(请参阅小提琴中的脚本标签)。 The code which is not library, and not UI code, ie the "real meat" of the transpiler has less than 100 lines (see function transpile ).不是库的代码,也不是 UI 代码,即转译器的“真肉”不到 100 行(参见 function transpile )。 So this might be a lot less complicated than you thought.所以这可能没有你想象的那么复杂。

I can't remember where I had seen the style recommendation, but I am certain that it was actually in multiple places.我不记得我在哪里看到过风格推荐,但我可以肯定它实际上在多个地方。 If you know or come across one such question, I invite you to be so kind to put the link into a comment under the question, I will mark useful.如果您知道或遇到这样一个问题,我邀请您将链接放入问题下方的评论中,我将标记为有用。 So far, there is one link, thank you Barmar.到目前为止,只有一个链接,谢谢 Barmar。

You might ask why I even bothered to write a "non-compliant" transpiler first, and did not go for the "compliant" version straight away.你可能会问为什么我什至费心先写一个“不兼容”的转译器,而没有立即为“兼容”版本编写 go。 That has to do with the estimated amount of work.这与估计的工作量有关。 I estimate the amount of work to be much larger for the "compliant" version.我估计“合规”版本的工作量要大得多 So much so, that it didn't really seem worthwhile to embark on such an endeavor.如此之多,以至于开始这样的努力似乎并不值得。 I would very much like to know whether this assessment of the amount of work is correct or incorrect.我很想知道这种对工作量的评估是正确的还是错误的。 Thus the question.因此问题。 Please do not insinuate a rhetorical, or even a dishonest motive for the question;请不要暗示问题的修辞,甚至是不诚实的动机; however weird it might sound to some, it really is the case that I'd like to be proven wrong, so please be so kind not to assume I'm "just saying that" for whatever reason, you'd be doing me an injustice.不管对某些人来说听起来多么奇怪,我确实希望被证明是错误的,所以请不要以为我出于任何原因“只是这么说”,你会对我做一个不公正。 This is, of all the questions I asked on SO so far, by far the one I've put the most work into.到目前为止,在我提出的所有问题中,这是迄今为止我投入最多的一个。 And, if you ask me, it is by far the best question I have ever asked here.而且,如果你问我,这是迄今为止我在这里问过的最好的问题。

Apart from someone assisting me in writing the "compliant" version of the transpiler, I'm also interested (albeit to a lesser degree) in anything objectively demonstrable which stands a chance to convince me that the "non-compliant" way is the wrong way.除了有人帮助我编写转译器的“合规”版本之外,我还对任何客观可证明的东西感兴趣(尽管程度较低),这有机会说服我“不合规”的方式是错误的方法。 Speed tests (with links to jsperf), reproducible bug reports, things of that sort.速度测试(带有 jsperf 的链接)、可重现的错误报告,诸如此类。

I should mention the speed tests I have done myself so far:我应该提到到目前为止我自己进行的速度测试:

first test , second test 第一次测试第二次测试

loosely related question 松散相关的问题

The better way really is to use return (if you can't completely refactor the tower).更好的方法是使用return (如果你不能完全重构塔)。 It makes it clear what you're doing and when you're returning a value from the intermediate functions;它清楚地说明了您在做什么以及何时从中间函数返回值; you can tell by looking at those functions where the result may be provided.您可以通过查看可能提供结果的那些函数来判断。 In contrast, using throw is invisible in those intermediate layers;相反,在那些中间层中使用throw是不可见的; it magically bypassing them in a way designed for error conditions.它以一种为错误条件设计的方式神奇地绕过它们。

If you don't want to do that, I don't think you have a reasonable alternative other than throw .如果您不想这样做,我认为您除了throw之外没有其他合理的选择。 I wondered about going down the route of generator functions or similar, but I think that would just complicate things more, not less.我想知道沿着生成器函数或类似函数的路线走下去,但我认为这只会使事情变得更加复杂,而不是更少。

Using return doesn't markedly complicate the example code, and does (to my eye) make it clearer what's going on and when results are potentially expected.使用return不会使示例代码明显复杂化,并且(在我看来)确实使正在发生的事情以及何时可能预期结果更清楚。

function wrapper(){
    function first(){
        function second(){
            function third(){
                doStuff4();
                some loop {
                    var result = ...
                    if (something) return result;
                }
            }
            doStuff2();
            let result = third();
            if (result) {
                return result;
            }
            doStuff3();
            return third();
        }
        doStuff1();
        return second();
    }
    return first() || "not found";
}

(In the above, result is tested for truthiness; substitute something else if appropriate.) (在上面, result是真实性的;如果合适的话,用其他东西代替。)

Ok, here is another approach with use of all async power of JavaScript... So basically I've recreated your nested functions but with use of Promise/await technique.好的,这是使用 JavaScript 的所有异步功能的另一种方法......所以基本上我已经重新创建了您的嵌套函数,但使用了 Promise/await 技术。 You will get result only once, the first time you'll resolve it from any level of nested functions.您只会得到一次结果,这是您第一次从任何级别的嵌套函数中解析它。 Everything else will be GC.其他一切都将是GC。 Check it out:一探究竟:

 // Create async function (async () => { const fnStack = (val) => { return new Promise((resolve, reject) => { // Worker functions. // Could be async; const doStuff1 = (val) => val + 1; const doStuff2 = (val) => val * 2; const doStuff3 = (val) => val * -1; // This will not affect result const doStuff4 = (val) => val + 1000; // Nested hell function first() { function second() { function third() { val = doStuff4(val); // Some loop for(let i = 0; i < 1000; i++) { if(i === 500) { // Here we got our result // Resolve it; resolve(val); } } } val = doStuff2(val); third(); // Below code will not affect // resolved result in third() above val = doStuff3(val); third(); } val = doStuff1(val); second(); } // first(); }). } // Run and get value const val = await fnStack(5); // We get our result ones console;log(val); })();

I think you should use arrays;我认为你应该使用 arrays;

const funcs = [first, second, third];
for(let i = 0; i < funcs.length; ++i){
 const result = funcs[i]();
 if (result) break;
}

You should return only from function that has the result.您应该只从具有结果的 function return

Sometime later, when I get around to it, I'll add instructions here on how to use my abstract syntax tree generation library which I used for my transpiler, maybe even start a little towards the other version, maybe explaining in more detail what the things are which make me think that it is more work.稍后,当我开始处理它时,我将在此处添加说明,说明如何使用我用于我的转译器的抽象语法树生成库,甚至可能从另一个版本开始,也许更详细地解释什么是事情是让我认为这更多的工作。

old version follows (will be deleted soon)旧版本如下(即将删除)

If the feature is used multiple times we'll need something like this:如果该功能被多次使用,我们将需要这样的东西:

function NestedThrowee(funcName, value){
    this.funcName = funcName;
    this.value = value;
}

return.someFunctionName someReturnValue (before compilation) will give (after compliation) something like return.someFunctionName someReturnValue (编译前)将给出(编译后)类似

var toBeThrown = new NestedThrowee("someFunctionName", someReturnValue);

And inside the generated catch block在生成的catch块内

if (e instanceof Error){
    throw e;   // re-throw "genuine" Error
} else {
    if (e instance of NestedThrowee){
    if (e.funcName === ... the name of the function to which
                       this catch block here belongs ...) return e.value;
    throw new Error('something happened which mathheadinclouds deemed impossible');
}

In the general case, it is necessary to wrap the result (or 'throwee') like shown above, because there could be multiple, possibly nested " nested returns ", and we have to take care that the catch phrase and caught object of type NestedThrowee match (by function name).在一般情况下,有必要像上面显示的那样包装结果(或 'throwee'),因为可能有多个,可能嵌套的“嵌套返回”,我们必须注意标语和catch类型的 object NestedThrowee匹配(按 function 名称)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM