19

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!

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Do you want currying? – Florian Margaine Jan 16 '15 at 09:58
  • No, it is not currying. It's basically to chain any wrapped functions, like Haskell IO monad. –  Jan 16 '15 at 10:00
  • 1
    I have a very naive doubt, `(log(1))` will be evaluated before `(log(4))` during evaluating `var world = (log('1'))(log('2'))(log('3'))(log('4'));`, right? – Anurag Peshne Jan 16 '15 at 10:21
  • Correct. Please see the first code. It is evaluated from head to tail. –  Jan 16 '15 at 10:24
  • 1
    The `seq` function does not return multiple values. In JavaScript when you write `expressionA expressionB` the interpreter first evaluates `expressionA`, ignores its result, and then evaluates `expressionB`. The entire expression evaluates to the result of `expressionB`. For example, `1, 2` evaluates to `2`; and `alert("Hello World!"), (1 + 2)` evaluates to `3` after alerting `Hello World!`. As [MDN](http://ow.ly/Htel1) puts it: the comma operator evaluates each of its operands (from left to right) and returns the value of the last operand. `seq :: IO a -> IO b -> IO b` ignores `IO a`. – Aadit M Shah Jan 17 '15 at 02:54
  • 1
    When I said that _"this is not possible in Haskell because one function can't return two results"_, I was referring to the `action` function, not the `seq` function. The `action(x)` returns either `x` or else `seq(x, y)` (wrapped in a superfluous function for laziness) depending upon whether or not you pass the argument `y`. It is not possible to implement the `action` function in Haskell because in Haskell functions when supplied with the same arguments must return the same results. It is possible to implement `seq` in Haskell (it is called `>>` in Haskell). `action` is a hack. Don't use it. – Aadit M Shah Jan 17 '15 at 03:04
  • For asynchronous IO actions you should check out promises: https://github.com/petkaantonov/bluebird – Aadit M Shah Jan 17 '15 at 03:50
  • @Aadit M Shah I understand clearly now. Thanks. I have known bluebird for a while, but I thought perhaps you can implement async feature somehow in extended manner of what you've done so far. In fact, I would not use external library along with this simple code. –  Jan 17 '15 at 08:41
  • Surely, it's best I could implement by my own, but so far I can't say I understand how everything works here, and I would ask you how difficult to implement async feature on this. Is it very hard and using an external promise library such as bluebird? Thanks. –  Jan 17 '15 at 08:45

4 Answers4

26

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.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • great answer. Thanks. So, do you think it is impossible to implement IO monad with just (f1)(f2)(f3)(f4) format like I wrote? Surely I can think of your `sequence`, it is `map` in short. And if possible I would not take that approach. –  Jan 16 '15 at 11:01
  • 1
    @KenOKABE I updated my answer showing how it's possible to chain IO actions the way you want. Also, `sequence` is not like `map`. It only looks like `map` in JavaScript because of the way we are representing IO actions. – Aadit M Shah Jan 16 '15 at 11:46
  • 2
    This is one of the greatest answer I have ever seen in stackoverflow. You are truly great. I confirmed your code works flawlessly, and surely satisfy all my intention. Also, this answer is extremely valuable to be refereed by all other people who study functional programming. Really appreciated. Thanks!!!! –  Jan 16 '15 at 12:51
  • 1
    The only misleading bit here is the fact of that synchronous IO is performed here where most IO in JS is asynchronous which would require the inner functions to return promises for instance. – Benjamin Gruenbaum Jan 16 '15 at 15:22
  • @BenjaminGruenbaum That's true. You would require a different way to model asynchronous IO actions. – Aadit M Shah Jan 16 '15 at 17:06
  • 1
    @AaditMShah you would require something similar to the IO monad, essentially a Promise with a then (bind, >>=) operation. – Benjamin Gruenbaum Jan 16 '15 at 17:08
  • @BenjaminGruenbaum Yes, a `Promise` is a monad too and `then` is indeed equivalent to `bind`. Promises allow for asynchronous IO, and that's way more interesting than the useless synchronous IO actions I described in my answer. – Aadit M Shah Jan 16 '15 at 17:18
  • Well hacked. But please don't ever use this in real code, when something goes wrong with code like this the debugging will be a nightmare. – aaaaaaaaaaaa Jan 16 '15 at 18:48
  • eBuisiness why do you say so? I wonder what kind of situation this hack makes dubugging hard. I disagree. –  Jan 16 '15 at 20:23
  • 1
    @KenOKABE When you make any sort of error and get a message like: "undefined is not a function" with a reference to one of the helper functions. Or worse, no error message at all, the call chain just broke. Code like this is a profound violation of the KISS principle. I'm getting ready to say "I told you so", this code is going to bite you, especially if you are not that well versed in JavaScript. – aaaaaaaaaaaa Jan 16 '15 at 20:43
  • @eBuisiness Thanks, but your statement sounds to me that you just prefer Procedural programming paradigm that is primitive and surely it's KISS in the sense. What we are talking about here is non-procedual, that is Declarative programming paradigm. Have I misunderstood you so far? –  Jan 16 '15 at 20:50
  • @Aadit M Shah I added further question on the topic of aysnc IO actions. I and other readesr would want badly for the implementation since this is JavaScript and functional. Thanks in advance. –  Jan 16 '15 at 22:07
  • 1
    @KenOKABE Mainly I object to adding too much abstraction on top of a language, for something like this to be worth it you need to gain a substantial improvement in your development cycle, and I don't see that happening. As for procedural coding, that is a programmer's bread and butter, you can use a lot of other paradigms, but they still incorporate blocks of procedural code. I know that pure declarative is possible, I'm not a fan, but I recognise that it has some merit. This way, however, I don't think that you will get much more than the worst of both paradigms. – aaaaaaaaaaaa Jan 17 '15 at 12:40
7

Let's look at what happens here:

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

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

And inline just a bit:

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

So we're returning a function f which accepts a function f1, and returns a function g which does logic and returns f1. Quite a mouthful! Your question is why does

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

Log 1. I did away with the log('4') since going to 3 is enough to show your described case. To answer that let's play compiler and do the inlining game!

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

Simple substitution. I took each instance of log(something), replaced it with the contents of the function, replaced the argument with the value passed. Let's do it again!

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

This one is a bit trickier to follow: I expanded the first function call. The topmost function received an argument f1 to which we just supplied a value, so I went into the function and replaced every occurrence of f1 with the given value (the result of log('2')), just like with the log argument.

Look over what happened here again if you still don't follow, but my suggestion is doing it yourself: Copy the snippet into your favourite code editor and do the expansion yourself.

Now you may see why the log('1') was called. The next thing we, the compiler, need to do is take care of the next function call. And whadya know, the first line in that function is a console.log! Better do it!

What can we do!?

I don't know Haskell or the IO Monad, but as you currently planned, I do not think you can do what you want with basic functions, not like that. If you can say what problem you want to solve using this...erm...pattern, maybe we can help!

Zirak
  • 38,920
  • 13
  • 81
  • 92
  • nice explanation: I couldn't explain like that :) – Naeem Shaikh Jan 16 '15 at 10:39
  • Well thanks 2 of you. The problem I want to solve is what you see, nothing more. Any application usage is based on this. It's not the optimization for a specific application usage. Thanks again. –  Jan 16 '15 at 10:43
  • To make this short. function composition. I will add this to my question. –  Jan 16 '15 at 10:51
  • @NaeemShaikh Hey Zirak. Perhaps my answer above will help you understand what the OP is asking. Also, the IO monad is just a data structure which allows one to write impure code in a pure language like Haskell. However, for impure languages like JavaScript there's no need for the IO monad because *everything is impure anyway*. Thus, essentially everything is in the IO monad because you can do IO any time you want. Hence, I'm not sure why the OP wants to implement the IO monad in JavaScript. Perhaps it's just a didactic exercise. I explained how the OP's problem can be solved in my answer above – Aadit M Shah Jan 16 '15 at 12:52
3

This is because you are just returning and returning everything...

There are three thing printed in the output:

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

2

1) first output: 1

this is because:console.log(args) is only executed once in your chaining, because f0 is executed only once at the last where it finds args to be 1(because of returning each nested function, what value you return at the last is a function f1 which executes f0() when the value of args is 1. then it prints 1 to the console.

2) second output the function f1

the return f1;(which is returned to the function when you passed args as 1) executed at the last returns

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

back to the variable world, hence only the inner nested function is printed to the console.

3) Third output: 2

then when you execute the functionworld(),

Again the function f1 is executed directly(see there is just a little difference between world and world()) but this time the returned function for when you passed args as 2.

Reason: world will output only the function, world() will execute the function.
When you write world(), at the last time where the function f1 is returned the value for args was 2 is directly executed.

I know I have terribly worded the answer.. but hope this helps (hope you understand)

gnat
  • 6,213
  • 108
  • 53
  • 73
Naeem Shaikh
  • 15,331
  • 6
  • 50
  • 88
2

While executing

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

(log('1')) is executed first which returns a function which takes in (log('2')).

This anonymous function starts executing but doesn't accept any argument. log('3') is neglected. This can verified by

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

After executing f0(); (which prints 1 to screen), we return f1 which points to function returned by log('2'), this takes in log('4');

This can be verified by doing: world()()()

this outputs:

2
4
undefined
Anurag Peshne
  • 1,547
  • 12
  • 29