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.
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?
retrun x, y;
multiple valueI 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!
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.