0

I understand that IO is used to separate pure code from impure one. Also, I am aware that IO allows for referential transparency.

One thing about IO is still a bit obscure to me though. Namely, a guarantee that between IO contained actions nothing bad can happen because it is all run at once when invoked (because it is nothing more than a lazy composition). So, when this lazy composition is finally invoked no other concurrent code can distort that (this lazy composition).

So is that really? Would IO be better (in this context) than a piece of code like this?

var x = 1; //shared resource

//some other code access and changes x to 2

const y = multiplyBy100(x);
const z = add1000(y);

log(z); // 1200 instead of desired 1100

I understand that IO is a solution to such a problem.

IO(function () {return x;}).map(multiplyBy100).map(add1000).map(log); //1100 no matter what

Is my reasoning ok?

Mateusz Wit
  • 425
  • 5
  • 10

1 Answers1

0

You thinking starts off in the right direction as it is true that IO monads allow for the separation of impure and pure actions. I Think you are missing something when you mention that

Also, I am aware that IO allows for referential transparency.

You might be mixing up the semantics of the IO itself with the benefits that they provide to your code outside of it. The IO doesn't so much allow for referential transparency in and of itself but instead, allows for functions that are referentially transparent (pure) to interface with the impure actions contained within the IO without losing their referential transparency.

When you compose an IO action and run it, the results are not guaranteed to be referentially transparent and that is exactly the point. This is not surprising when we are armed with the understanding that IO is the dumping ground for impure actions.

So, when this lazy composition is finally invoked no other concurrent code can distort that (this lazy composition).

Unfortunately, it isn't quite so simple. Code being lazy doesn't make it referentially transparent. The evaluation of a chain of functions such as an IO being delayed (lazy), has no bearing on the values upon which they are operating on.

In the context of javascript, all values are references. Any function on the stack that has within its scope, a reference, can change the value of said reference. Let me show you what this means for your own example code showing the IO returning the value of 1100 no matter what. When I change the value of x before or after declaring the IO wrapping x, the value returned by the IO will reflect such a change. Therefore this code won't return //1100 no matter what but will infact, return 1200.

function switcharoo() {
  x = 2;
}

var x = 1;

var IOdoMaths = IO(() => x).map(x => x * 100).map(x => x + 1000);

// Some concurrent proccess calls this
switcharoo();

var result = IOdoMaths.runIO();

console.log(result); // 1200

We were expecting the IO action to result in 1100, but istead we got 1200. Remember, when we created our IO, we wraped a reference and not a value. This holds true for any type of closure in Javascript. it doesn't matter if you stuff a closure in an IO after you declare it, the same rules are going to apply.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375