繁体   English   中英

JavaScript链接的函数组成

[英]JavaScript function composition by chaining

我检查了重复问题的可能性,但找不到确切的解决方案。

我在JavaScript中编写了一些函数链代码,如下所示,并且工作正常。

var log = function(args)
{
  console.log(args)

  return function(f)
  {
    return f;
  };
};

(log('1'))(log('2'))(log('3'))(log('4'));

//1
//2
//3
//4

我想做这个懒惰的评估。

或者撰写功能。

var log = function(args)
{
  var f0 = function()
  {
    return console.log(args);
  };

  return function(f1)
  {
    return function()
    {
      f0();
      return f1;
    };
  };
};

var world = (log('1'))(log('2'))(log('3'))(log('4'));
console.log(world);
//should be just a function,
// but in fact
//1
//[function]

world();
//should be
//1
//2
//3
//4

// but in fact
// 2

有些事情是非常错误的。 你能修好它吗?

谢谢。

这个问题已经解决,但还有进一步的问题

注释讨论中显示的异步问题

当我们有

// unit :: a -> IO a
var unit = function(x)
{
  return function()
  {
    return x;
  };
};

// bind :: IO a -> (a -> IO b) -> IO b
var bind = function(x, y)
{
  return function()
  {
    return y(x())();
  };
};

// seq :: IO a -> IO b -> IO b
var seq = function(x, y)
{
  return function()
  {
    return x(), y();
  };
};

var action = function(x)
{
  return function(y)
  {
    return y ? action(seq(x, y)) : x();
  };
};

var wrap = function(f)
{
  return function(x)
  {
    return action(function()
    {
      return f(x);
    });
  };
};

var log = wrap(console.log);



// -- runtime -- 
// HACK: when `world` is modified by passing a function,
//       the function will be executed.

Object.defineProperties(window,
{
  world:
  {
    set: function(w)
    {
      return w();
    }
  }
});

我们也常常非常想要异步连锁反应。

var asyncF = function(callback)
{
  setTimeout(function()
  {
    for (var i = 0; i < 1000000000; i++)
    {

    };

    callback("async process Done!");
  }, 0);
};

var async = wrap(asyncF(function(msg)
{
  world = log(msg);

  return msg;
}));

现在,

world = (log(1))(async)(log(3));
//1
//3
//async process Done!

到目前为止,顺利,现在我们尝试使用绑定

world = (log(1))
  (bind((async), (log(x))));

//should be
//1
//async process Done!
//3

//in fact
//ReferenceError: x is not defined

你可以修改一下吗?

还有一个关于retrun x, y; 多重价值

我不明白

  // seq :: IO a -> IO b -> IO b
    var seq = function(x, y)
    {
      return function()
      {
        return x(), y();
      };
    };

正如图书馆作者所提到的那样

请注意,这在Haskell中是不可能的,因为一个函数不能返回两个结果。 而且,在我的拙见中,它看起来很难看。

我同意,不知道这是什么

return x(), y();

多重回报值。

我在谷歌搜索并搜索,但找不到答案。

这是什么??

(以防万一,我会选择这个hack语法)

谢谢!

因此,如果我正确理解了这个问题,您希望在JavaScript中链接IO操作。 为此,首先需要定义IO操作的内容。 想到IO动作的一种方法是它只是一个不带参数的函数。 例如:

// log :: a -> IO b

function log(x) {
    return function () {       // IO action
        return console.log(x);
    };
}

将IO操作表示为不带参数的函数的一个优点是它与thunk未评估的表达式 )的表示相同。 Thunk是能够在像Haskell这样的语言中进行延迟评估的东西。 因此,你可以免费得到懒惰。

现在组成。 你如何在JavaScript中组成两个IO动作? 在Haskell中,使用>>运算符对IO操作进行排序,IO操作通常按照>>= (aka bind )定义,如下所示:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

(>>) :: Monad m => m a -> m b -> m b
x >> y = x >>= \_ -> y

在JavaScript中为IO动作编写等效的bind函数很容易:

// bind :: IO a -> (a -> IO b) -> IO b

function bind(x, y) {
    return function () {
        return y(x())();
    };
}

假设你有一个IO动作x :: IO a 因为它只是一个没有参数的函数,所以当你调用它时它等同于评估IO动作。 因此x() :: a 将此结果提供给函数y :: a -> IO b导致IO操作y(x()) :: IO b 请注意,整个操作包含在多余的懒惰功能中。

同样,实现>>运算符同样简单。 我们称之为“序列”中的seq

// seq :: IO a -> IO b -> IO b

function seq(x, y) {
    return function () {
        return x(), y();
    };
}

这里我们评估IO表达式x ,不关心它的结果然后返回IO表达式y 这正是>>运算符在Haskell中的作用。 请注意,整个操作包含在多余的懒惰功能中。

Haskell还有一个return函数,它将值提升为monadic上下文。 由于return是JavaScript中的关键字,我们将其称为unit

// unit :: a -> IO a

function unit(x) {
    return function () {
        return x;
    };
}

事实证明,Haskell中还有一个sequence运算符,它对列表中的monadic值进行排序。 它可以在JavaScript中实现,用于IO操作,如下所示:

// sequence :: [IO a] -> IO [a]

function sequence(array) {
    return function () {
        var list   = array;
        var length = list.length;
        var result = new Array(length);
        var index  = 0;

        while (index < length)
            result[index] = list[index++]();
        return result;
    };
}

这就是我们所需要的一切。 现在我们可以写:

var world = sequence([log("1"), log("2"), log("3"), log("4")]);

world();

// 1
// 2
// 3
// 4

希望有所帮助。


是的,确实可以使用您的语法链接IO操作。 但是,我们需要重新定义IO操作的内容:

function action(x) {
    return function (y) {
        return y ? action(seq(x, y)) : x();
    };
}

让我们通过一个例子来理解action函数的action

// log :: a -> IO b
// log :: a -> IO r -> IO r

function log(x) {
    return action(function () {
        return console.log(x);
    });
}

现在你可以这样做:

log("1")();         // :: b
log("1")(log("2")); // :: IO r

在第一种情况下,我们评估了IO操作log("1") 在第二种情况下,我们对IO动作log("1")log("2")排序。

这允许你这样做:

var world = (log("1"))(log("2"))(log("3"))(log("4"));

world();

// 1
// 2
// 3
// 4

另外你还可以这样做:

var newWorld = (world)(log("5"));

newWorld();

// 1
// 2
// 3
// 4
// 5

等等....

其他一切都是一样的。 请注意,这在Haskell中是不可能的,因为一个函数不能返回两个结果。 而且,在我的拙见中,它看起来很难看。 我更喜欢使用sequence 但是,这就是你想要的。

让我们来看看这里发生了什么:

var log = function(args)
{
  var f0 = function()
  {
    return console.log(args);
  };

  return function(f1)
  {
    return function()
    {
      f0();
      return f1;
    };
  };
};

并且内联了一下:

var log = function(args) {
  return function(f1) {
    return function() {
      console.log(args);
      return f1;
    };
  };
};

所以,我们正在返回一个函数f它接收一个函数f1 ,并返回一个函数g这的确逻辑并返回f1 相当满口! 你的问题是为什么

(log('1'))(log('2'))(log('3'));

记录1 我取消了log('4')因为转到3足以显示您描述的情况。 要回答这个问题,让我们玩编译器并进行内联游戏吧!

(log('1'))(log('2'))(log('3'))
// =>
(
  function (f1) {
    return function () {
      console.log('1');
      return f1;
    }
  }
)(
  function (f1) {
    return function () {
      console.log('2');
      return f1;
    }
  }
)(
  function (f1) {
    return function () {
      console.log('3');
      return f1;
    }
  }
)

简单的替代。 我拿了每个log(something)实例,用函数的内容替换它,用传递的值替换参数。 让我们再来一次!

(
  function () {
    console.log('1');
    return function (f1) {
      return function () {
        console.log('2');
        return f1;
      }
    };
  }
)(
  function (f1) {
    return function () {
      console.log('3');
      return f1;
    }
  }
)

这个有点棘手:我扩展了第一个函数调用。 最上面的函数接收到一个参数f1 ,我们刚刚提供了一个值,所以我进入函数并用给定的值( log('2')的结果)替换每次出现的f1 ,就像使用log参数一样。

如果你仍然不遵循,请再次查看此处发生的事情,但我的建议是自己动手:将代码段复制到您喜欢的代码编辑器中并自行进行扩展。

现在您可能会看到为什么调用log('1') 我们编译器需要做的下一件事就是处理下一个函数调用。 而whadya知道,该函数的第一行是console.log 做得更好!

我们能做什么!?

我不知道Haskell或IO Monad,但按照你目前的计划,我认为你不能用基本功能做你想做的事情,而不是那样。 如果你能用这个...呃......模式说出你想解决什么问题,也许我们可以提供帮助!

这是因为你只是回来并归还一切......

输出中打印了三件事:

1
 function ()
    {
      f0();
      return f1;
    }

2

1)第一输出:1

这是因为: console.log(args)只在你的链接中执行一次,因为f0只在最后找到args为1时执行一次(因为返回每个嵌套函数,你在最后返回的值是函数f1,当args的值为1时执行f0(),然后将1打印到控制台。

2)第二次输出函数f1

return f1; (在你将args传递为1时返回给函数)在最后一次返回时执行

  function ()
            {
              f0();
              return f1;
            }

回到变量世界,因此只有内部嵌套函数被打印到控制台。

3)第三输出:2

然后当你执行函数world()

函数f1再次直接执行(请参阅worldworld()之间的一点点差异)但这次返回的函数是将args传递给2时的函数。

原因:世界将仅输出函数, world()将执行该函数。
当你编写world() ,在返回function f1的最后一次,args的值是2,直接执行。

我知道我的答案非常措辞..但希望这会有所帮助(希望你理解)

执行时

var world = (log('1'))(log('2'))(log('3'))(log('4'));

(log('1'))首先执行,返回一个接收的函数(log('2'))。

此匿名函数开始执行但不接受任何参数。 log('3')被忽略了。 这可以通过验证

if(typeof(arguments[0]) == 'function'){
    console.log("Got a neglected argument");
    console.log(arguments[0]());
}

执行f0(); (将1打印到屏幕上),我们返回指向log('2')返回的函数的f1,这将记录log('4');

这可以通过执行以下操作来验证: world()()()

这个输出:

2
4
undefined

暂无
暂无

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

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