Search code examples
javascriptnode.jsmonadsio-monad

JavaScript function composition by chaining


I have checked the possibility of duplicate question, and cannot find the exact solution.

I wrote some function chain code in JavaScript as below, and works fine.

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

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

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

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

I want to make this lazy evaluation.

Or to compose function.

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

Something is very wrong. Can you fix it?

Thanks.

This question is resolved, but there is further

async issue as shown in comment discussion

When we have

// 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();
    }
  }
});

We also often want async chain reactions badly.

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;
}));

Now,

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

So far nice and smooth, now we try to use bind

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

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

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

Could you modify to make this work, please?

one more about retrun x, y; multiple value

I don't understand

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

as the library author mentions

Note that this is not possible in Haskell because one function can't return two results. Also, in my humble opinion, it looks ugly.

I agree, and don't know what this

return x(), y();

multiple return value.

I googled and searched here, but could not find an answer.

What is this??

(just in case, I will chose this hack for the syntax)

Thanks!


Solution

  • So if I understand the question correctly, you want to chain IO actions in JavaScript. To do so, you first need to define what an IO action is. One way to think of an IO action is that it is simply a function which takes no arguments. For example:

    // log :: a -> IO b
    
    function log(x) {
        return function () {       // IO action
            return console.log(x);
        };
    }
    

    One advantage of representing an IO action as a function with no arguments is that it is the same representation for thunks (unevaluated expressions). Thunks are the things that enable lazy evaluation in languages like Haskell. Hence you get laziness for free.

    Now composition. How do you compose two IO actions in JavaScript? In Haskell, you use the >> operator to sequence IO actions, which is usually defined in terms of >>= (a.k.a. bind) as follows:

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

    It is easy to write an equivalent bind function for our IO actions in JavaScript:

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

    Suppose you have an IO action x :: IO a. Since it's just a function with no arguments, when you call it it's equivalent to evaluating the IO action. Hence x() :: a. Feeding this result to the function y :: a -> IO b results in the IO action y(x()) :: IO b. Note that the entire operation is wrapped in a superfluous function for laziness.

    Similarly, it is just as straightforward to implement the >> operator. Let's call it seq as in “sequence”.

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

    Here we evaluate the IO expression x, don't care about its result and then return the IO expression y. This is exactly what the >> operator does in Haskell. Note that the entire operation is wrapped in a superfluous function for laziness.

    Haskell also has a return function which lifts a value into a monadic context. Since return is a keyword in JavaScript, we'll call it unit instead:

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

    As it turns out there's also a sequence operator in Haskell which sequences monadic values in a list. It can be implemented in JavaScript for IO actions as follows:

    // 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;
        };
    }
    

    That's all we need. Now we can write:

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

    Hope that helps.


    Yes, it is indeed possible to chain IO actions using your syntax. However, we'll need to redefine what an IO action is:

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

    Let's understand what the action function does using an example:

    // log :: a -> IO b
    // log :: a -> IO r -> IO r
    
    function log(x) {
        return action(function () {
            return console.log(x);
        });
    }
    

    Now you can do:

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

    In the first case we evaluated the IO action log("1"). In the second case we sequenced the IO actions log("1") and log("2").

    This allows you to do:

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

    In addition you can also do:

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

    And so on....

    Everything else remains the same. Note that this is not possible in Haskell because one function can't return two results. Also, in my humble opinion, it looks ugly. I prefer using sequence instead. However, this is what you wanted.