gpt4 book ai didi

javascript - 比function(){return x}更简洁的延迟评估?

转载 作者:行者123 更新时间:2023-12-03 11:17:46 25 4
gpt4 key购买 nike

我正在移植一些严重依赖延迟评估的Python代码。这可以通过thunks完成。更具体地说,任何需要延迟评估的Python表达式<expr>都被封装在Python“lambda表达式”中,即lambda:<expr>

AFAIK,与此最接近的JavaScript等效项是function(){return <expr>}

由于我正在使用的代码在这样的重击中绝对泛滥成灾,因此,我想尽可能使它们的代码更简洁。这样做的原因不仅是为了保存字符(在使用JS时不可忽略的考虑),而且还使代码更具可读性。要了解我的意思,请比较以下标准JavaScript形式:

function(){return fetchx()}


\fetchx()

在第一种形式中,实质信息,即 fetchx()表达式在打印上被周围的 function(){return ... }遮盖。在第二种形式1中,仅一个( \)字符用作“延迟评估标记”。我认为这是最佳方法。

AFAICT,针对此问题的解决方案可以归为以下几类:
  • 使用eval模拟延迟评估。
  • 一些我不知道的特殊JavaScript语法,可以实现我想要的功能。 (我对JavaScript的极大了解使这种可能性在我看来非常真实。)
  • 用一些非标准JavaScript编写代码,这些代码会通过编程方式处理为正确的JavaScript。 (当然,这种方法不会减少最终代码的占用空间,但至少可以保留一些可读性。)
  • 以上都不是。

  • 我对听到最近三类的回复特别感兴趣。

    P.S .:我知道JS世界中已广泛禁止使用 eval(上面的选项1),但是FWIW在下面给出了一个有关此选项的说明。

    想法是定义一个私有(private)包装器类,其唯一目的是将纯字符串标记为JavaScript代码以进行延迟评估。然后使用具有短名称(例如 C,表示“CODE”)的工厂方法来减少例如
    function(){return fetchx()}


    C('fetchx()')

    首先,定义工厂 C和辅助函数 maybe_eval:
    var C = (function () {
    function _delayed_eval(code) { this.code = code; }
    _delayed_eval.prototype.val = function () { return eval(this.code) };
    return function (code) { return new _delayed_eval(code) };
    })();

    var maybe_eval = (function () {
    var _delayed_eval = C("").constructor;
    return function (x) {
    return x instanceof _delayed_eval ? x.val() : x;
    }
    })();
    get函数和 lazyget函数之间的以下比较显示了如何使用以上内容。

    这两个函数都带有三个参数:对象 obj,键 key和默认值,并且如果 obj[key]中存在 key,则它们都应返回 obj,否则返回默认值。

    这两个函数之间的唯一区别是 lazyget的默认值可以是thunk,如果是,则只有 key不在 obj中时,它的值才会被求值。
    function get(obj, key, dflt) {
    return obj.hasOwnProperty(key) ? obj[key] : dflt;
    }

    function lazyget(obj, key, lazydflt) {
    return obj.hasOwnProperty(key) ? obj[key] : maybe_eval(lazydflt);
    }

    在操作中看不到这两个功能,请定义:
    function slow_foo() {
    ++slow_foo.times_called;
    return "sorry for the wait!";
    }
    slow_foo.times_called = 0;

    var someobj = {x: "quick!"};

    然后,在对上述内容进行评估之后,并使用(例如)Firefox + Firebug,执行以下操作
    console.log(slow_foo.times_called)              // 0

    console.log(get(someobj, "x", slow_foo())); // quick!
    console.log(slow_foo.times_called) // 1

    console.log(lazyget(someobj, "x",
    C("slow_foo().toUpperCase()"))); // quick!
    console.log(slow_foo.times_called) // 1

    console.log(lazyget(someobj, "y",
    C("slow_foo().toUpperCase()"))); // SORRY FOR THE WAIT!
    console.log(slow_foo.times_called) // 2

    console.log(lazyget(someobj, "y",
    "slow_foo().toUpperCase()")); // slow_foo().toUpperCase()
    console.log(slow_foo.times_called) // 2

    打印出来
    0
    quick!
    1
    quick!
    1
    SORRY FOR THE WAIT!
    2
    slow_foo().toUpperCase()
    2

    1 ...可能会让Haskell程序员感到陌生。 :)

    2还有另一种方法,例如Mathematica使用的一种方法,完全避免了延迟评估标记的需要。在这种方法中,作为函数定义的一部分,可以为非标准评估指定其形式参数中的任何一个。从打印上来说,这种方法当然最大程度地不引人注目,但就我的口味而言,实在太多了。此外,恕我直言,它不像使用例如 \作为延迟评估标记那样灵活。

    最佳答案

    以我的拙见,我认为您是从错误的 Angular 看待这个问题。如果要手动创建thunk,则需要考虑重构代码。在大多数情况下,重击应该是:

  • 从惰性函数返回的任何一个。
  • 或通过组合函数创建。

  • 从懒惰函数返回重击

    当我第一次开始用JavaScript进行函数式编程时,我被 Y combinator迷住了。根据我在网上阅读的内容,Y组合器是一个值得崇拜的神圣实体。它以某种方式允许不知道自己名称的函数调用它们自己。因此,它是递归的数学表现形式-函数编程最重要的支柱之一。

    但是,了解Y组合器并非易事。 Mike Vanier wrote认为,Y组合器的知识是那些“具有功能素养”的人与不具备“功能素养”的人之间的跳水线。老实说,Y组合器本身很简单易懂。但是,大多数在线文章向后解释它,因此很难理解。例如,维基百科将Y组合器定义为:
    Y = λf.(λx.f (x x)) (λx.f (x x))

    在JavaScript中,这将转换为:
    function Y(f) {
    return (function (x) {
    return f(x(x));
    }(function (x) {
    return f(x(x));
    }));
    }

    Y组合器的这种定义是不直观的,也无法使Y组合器如何体现递归。更不用说它根本不能在像JavaScript这样的急切语言中使用,因为立即对 x(x)表达式求值会导致无限循环,最终导致堆栈溢出。因此,在像JavaScript这样的急切语言中,我们改用Z组合器:
    Z = λf.(λx.f (λv.((x x) v))) (λx.f (λv.((x x) v)))

    JavaScript中产生的代码更加令人困惑和不直观:
    function Z(f) {
    return (function (x) {
    return f(function (v) {
    return x(x)(v);
    });
    }(function (x) {
    return f(function (v) {
    return x(x)(v);
    });
    }));
    }

    显而易见,Y组合器和Z组合器之间的唯一区别是,懒惰的表达式 x(x)被渴望的表达式 function (v) { return x(x)(v); }代替。它包裹在一个大块的东西里。但是,在JavaScript中,编写如下代码的thunk更有意义:
    function () {
    return x(x).apply(this, arguments);
    }

    当然,这里我们假设 x(x)评估为一个函数。对于Y组合器,这确实是正确的。但是,如果thunk不求值一个函数,那么我们只返回表达式。

    对我来说,作为程序员最重要的时刻之一就是Y组合器本身就是递归的。例如,在Haskell中,您可以如下定义Y组合器:
    y f = f (y f)

    由于Haskell是一种惰性语言,因此仅在需要时才评估 y f中的 f (y f),因此您不会遇到无限循环。在内部,Haskell为每个表达式创建一个thunk。但是,在JavaScript中,您需要显式创建一个thunk:
    function y(f) {
    return function () {
    return f(y(f)).apply(this, arguments);
    };
    }

    当然,递归地定义Y组合器是作弊:您只是在Y组合器内显式地进行递归。在数学上,Y组合器本身应以非递归方式定义,以描述递归的结构。尽管如此,我们都喜欢它。重要的是JavaScript中的Y组合器现在返回一个thunk(即,我们使用惰性语义定义了它)。

    为了巩固我们的理解,让我们在JavaScript中创建另一个惰性函数。让我们在JavaScript中实现Haskell的 repeat函数。在Haskell中, repeat函数定义如下:
    repeat :: a -> [a]
    repeat x = x : repeat x

    如您所见, repeat没有边缘情况,它递归地调用自身。如果Haskell不那么懒惰,它将永远递归。如果JavaScript是惰性的,那么我们可以实现 repeat,如下所示:
    function repeat(x) {
    return [x, repeat(x)];
    }

    不幸的是,如果执行了上面的代码,它将永远递归直到导致堆栈溢出。为了解决这个问题,我们返回一个thunk:
    function repeat(x) {
    return function () {
    return [x, repeat(x)];
    };
    }

    当然,由于thunk无法评估函数,因此我们需要另一种方式来相同地对待thunk和正常值。因此,我们创建了一个函数来评估一个thunk,如下所示:
    function evaluate(thunk) {
    return typeof thunk === "function" ? thunk() : thunk;
    }

    现在可以使用 evaluate函数来实现可以将惰性数据结构或严格数据结构作为参数的函数。例如,我们可以使用 take从Haskell实现 evaluate函数。在Haskell中, take定义如下:
    take :: Int -> [a] -> [a]
    take 0 _ = []
    take _ [] = []
    take n (x:xs) = x : take (n - 1) xs

    在JavaScript中,我们将使用 take来实现 evaluate,如下所示:
    function take(n, list) {
    if (n) {
    var xxs = evaluate(list);
    return xxs.length ? [xxs[0], take(n - 1, xxs[1])] : [];
    } else return [];
    }

    现在,您可以按以下方式一起使用 repeattake:
    take(3, repeat('x'));

    亲自观看演示:

    alert(JSON.stringify(take(3, repeat('x'))));

    function take(n, list) {
    if (n) {
    var xxs = evaluate(list);
    return xxs.length ? [xxs[0], take(n - 1, xxs[1])] : [];
    } else return [];
    }

    function evaluate(thunk) {
    return typeof thunk === "function" ? thunk() : thunk;
    }

    function repeat(x) {
    return function () {
    return [x, repeat(x)];
    };
    }


    工作时的惰性评估。

    以我的拙见,大多数重击应该是那些由懒惰函数返回的重击。您永远不必手动创建thunk。但是,每次创建惰性函数时,仍然需要在其中手动创建thunk。可以通过以下方式提升惰性函数来解决此问题:
    function lazy(f) {
    return function () {
    var g = f, self = this, args = arguments;

    return function () {
    var data = g.apply(self, args);
    return typeof data === "function" ?
    data.apply(this, arguments) : data;
    };
    };
    }

    使用 lazy函数,您现在可以如下定义Y组合器和 repeat:
    var y = lazy(function (f) {
    return f(y(f));
    });

    var repeat = lazy(function (x) {
    return [x, repeat(x)];
    });

    这使得JavaScript中的函数式编程几乎与Haskell或OCaml中的函数式编程一样有趣。请参阅更新的演示:

    var repeat = lazy(function (x) {
    return [x, repeat(x)];
    });

    alert(JSON.stringify(take(3, repeat('x'))));

    function take(n, list) {
    if (n) {
    var xxs = evaluate(list);
    return xxs.length ? [xxs[0], take(n - 1, xxs[1])] : [];
    } else return [];
    }

    function evaluate(thunk) {
    return typeof thunk === "function" ? thunk() : thunk;
    }

    function lazy(f) {
    return function () {
    var g = f, self = this, args = arguments;

    return function () {
    var data = g.apply(self, args);
    return typeof data === "function" ?
    data.apply(this, arguments) : data;
    };
    };
    }


    通过组合函数创建Thunk

    有时您需要将表达式传递给延迟计算的函数。在这种情况下,您需要创建自定义文件。因此,我们无法使用 lazy函数。在这种情况下,您可以使用功能组合作为手动创建样式的可行选择。函数组成在Haskell中定义如下:
    (.) :: (b -> c) -> (a -> b) -> a -> c
    f . g = \x -> f (g x)

    在JavaScript中,这表示为:
    function compose(f, g) {
    return function (x) {
    return f(g(x));
    };
    }

    但是,将其编写为:
    function compose(f, g) {
    return function () {
    return f(g.apply(this, arguments));
    };
    }

    数学中的函数组成从右到左读取。但是,JavaScript中的评估始终是从左到右。例如,在表达式 slow_foo().toUpperCase()中,首先执行 slow_foo函数,然后在其返回值上调用 toUpperCase方法。因此,我们想以相反的顺序组成函数并将它们链接如下:
    Function.prototype.pipe = function (f) {
    var g = this;

    return function () {
    return f(g.apply(this, arguments));
    };
    };

    使用 pipe方法,我们现在可以组成如下功能:
    var toUpperCase = "".toUpperCase;
    slow_foo.pipe(toUpperCase);

    上面的代码将等效于以下代码:
    function () {
    return toUpperCase(slow_foo.apply(this, arguments));
    }

    但是有一个问题。 toUpperCase函数实际上是一种方法。因此, slow_foo返回的值应设置 thistoUpperCase指针。简而言之,我们希望将 slow_foo的输出通过管道传递到 toUpperCase中,如下所示:
    function () {
    return slow_foo.apply(this, arguments).toUpperCase();
    }

    该解决方案实际上非常简单,我们根本不需要修改 pipe方法:
    var bind = Function.bind;
    var call = Function.call;

    var bindable = bind.bind(bind); // bindable(f) === f.bind
    var callable = bindable(call); // callable(f) === f.call

    使用 callable方法,我们现在可以如下重构代码:
    var toUpperCase = "".toUpperCase;
    slow_foo.pipe(callable(toUpperCase));

    由于 callable(toUpperCase)toUpperCase.call等效,因此现在的重击为:
    function () {
    return toUpperCase.call(slow_foo.apply(this, arguments));
    }

    这正是我们想要的。因此,我们的最终代码如下:
    var bind = Function.bind;
    var call = Function.call;

    var bindable = bind.bind(bind); // bindable(f) === f.bind
    var callable = bindable(call); // callable(f) === f.call

    var someobj = {x: "Quick."};

    slow_foo.times_called = 0;

    Function.prototype.pipe = function (f) {
    var g = this;

    return function () {
    return f(g.apply(this, arguments));
    };
    };

    function lazyget(obj, key, lazydflt) {
    return obj.hasOwnProperty(key) ? obj[key] : evaluate(lazydflt);
    }

    function slow_foo() {
    slow_foo.times_called++;
    return "Sorry for keeping you waiting.";
    }

    function evaluate(thunk) {
    return typeof thunk === "function" ? thunk() : thunk;
    }

    然后我们定义测试用例:
    console.log(slow_foo.times_called);
    console.log(lazyget(someobj, "x", slow_foo()));

    console.log(slow_foo.times_called);
    console.log(lazyget(someobj, "x", slow_foo.pipe(callable("".toUpperCase))));

    console.log(slow_foo.times_called);
    console.log(lazyget(someobj, "y", slow_foo.pipe(callable("".toUpperCase))));

    console.log(slow_foo.times_called);
    console.log(lazyget(someobj, "y", "slow_foo().toUpperCase()"));

    console.log(slow_foo.times_called);

    结果与预期的一样:
    0
    Quick.
    1
    Quick.
    1
    SORRY FOR KEEPING YOU WAITING.
    2
    slow_foo().toUpperCase()
    2

    因此,如您所见,在大多数情况下,您无需手动创建thunk。使用函数 lazy的提升函数可以使它们返回thunk,或者使用组合函数来创建新的thunk。

    关于javascript - 比function(){return x}更简洁的延迟评估?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18541297/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com