() => console.log(“abc”)
Is this really so different from IO everywhere in Haskell? () => console.log(“abc”)
Is this really so different from IO everywhere in Haskell?The real sell is: Haskell objects which are not Maybe are definitely there. Functions which are not IO will always give the same input for the same output.
So print(s) returns a function that when called prints s. Then there needs to be function that joins those functions, so print(a); print(b) evaluates to a function that once called prints out a and then b.
What makes Haskell special in my opinion is 1) it generalises this way of achieving "stateful" functions, 2) enforces such discipline for you and makes sure calling functions never produces side effects, and 3) some syntactic sugar (do, <-, etc) to make it easier to write this kind of code.
Also note that the above example only does output which would be the easier case. When it comes to code with input, there will suddenly be values which are unavailable until some side effects have been made, so the returned functions will also need to encode how those values are to be obtained and used, which complicates things further.
It's a starting point. You'll have to somehow chain (e.g. you want to print "def" after "abc", how would you do that?). You'll also need to put a value into your IO, e.g. if you are inside that IO, all the computations will need to produce IO. If you solve those things, you'll have a Monad.
But, wait! You're in JS, right? You have IO Monads there, perhaps only don't know yet. They are the JS Promises! Look this:
// assuming that you have a sync input function, that receives a string from stdin and returns it
let input_str = new Promise((resolve, reject) => resolve(input());
let input_int = input_str.then(s => parseInt(s));
let square = input_int.then(n => n * n);
square.then(sq => console.log(sq));
If you want to proceed with your poor man's IO, you'll need to implement your "then". Going further, the equivalent in Haskell (not the best code, though) to that is:
main = result
where input_str = getLine
input_int = input_str >>= \s -> return (read s) :: IO Int
square = input_int >>= \n -> return (n * n)
result = square >>= \sq -> putStrLn (show sq)
Ok, but Haskell has a syntax for cleaning that, called do notation:main = do
input_str <- getLine
let input_int = read input_str :: Int
let square = input_int * input_int
putStrLn (show square)
And JS? JS has also its syntax, using await. Just like Haskell that it only works inside the do notation, in JS it will only work inside async functions:let input_str = await new Promise((resolve, reject) => resolve(input()));
let input_int = parseInt(input_str);
let square = input_int * input_int;
console.log(square);
In those senses, these are the same. You can emulate the IO monad in this way.
Haskell provides guarantees that make this stronger. There is no way to perform IO-like side effects except via the IO monad. And no way to “evaluate” the IO monad except to declare it as your main entry point, compile, and hit play.
(Except unsafePerformIO, of course, the highly discouraged escape hatch).
These restrictions are actually what makes the use of the IO monad powerful. As you show, it’s easy to “add” it to a language, and also almost valueless. The value is in removing everything that’s not in the IO monad.