JavaScript链接的函数组成

时间:2015-01-16 09:22:57

标签: javascript node.js

我检查了重复问题的可能性, 并且无法找到确切的解决方案。

我在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!

到目前为止一切顺利,现在我们尝试使用bind

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来获取语法)

谢谢!

4 个答案:

答案 0 :(得分:26)

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

// log :: a -> IO b

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

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

现在组成。你如何在JavaScript中组成两个IO动作?在Haskell中,您使用>>运算符对IO操作进行排序,这些操作通常按>>=(a.k.a。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函数的作用:

// 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。但是,这就是你想要的。

答案 1 :(得分:7)

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

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,它执行 logic 并返回{{1} }。相当满口!你的问题是为什么

f1

记录(log('1'))(log('2'))(log('3')); 。我放弃了1因为转到3就足以显示你所描述的情况了。要回答这个问题,让我们玩编译器并进行内联游戏!

log('4')

简单替换。我接受了(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的结果)替换了f1的每一次出现,就像log('2')参数一样。

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

现在您可以看到调用log的原因了。我们编译器需要做的下一件事就是处理下一个函数调用。而且whadya知道,该函数的第一行是log('1')!做得更好!

我们能做什么!?

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

答案 2 :(得分:3)

这是因为你刚回来并归还所有东西......

输出中印有三件事:

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

2
  

1)第一次输出:1

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

  

2)第二次输出函数f1

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

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

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

  

3)第三输出:2

然后执行函数world()

再次直接执行函数f1(参见worldworld()之间只有一点点差异)但这次是将args作为{{1}传递的返回函数}}

原因:世界只输出函数,2将执行该函数 当您编写world()时,在返回world()的最后一次,args的值为2将直接执行。

我知道我的答案非常措辞......但希望这有助于(希望你明白)

答案 3 :(得分: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