I am completely lost in trying to translate the following Haskell code into Javascript:
instance MonadFix IO where
mfix f = do
var <- newEmptyMVar -- 1
ans <- unsafeInterleaveIO $ takeMVar var -- 2
result <- f ans -- 3
putMVar var result -- 4
return result -- 5
Let’s walk through this line by line in terms of our little time travel analogy:
- Create an empty mutable variable.
- Predict the future value that will be contained in that mutable variable.
- Call the function f with the predicted future value.
- Store the result of f in the variable, thus fulfilling the prophecy as required by line 2.
- Return that result.
What I have is a special Lazy
type that handles (or rather defers) synchronous IO
. However, I'd like to abstract from direct recursion with value recursion:
const record = (type, o) =>
(o[Symbol.toStringTag] = type.name || type, o);
const Lazy = lazy => record(Lazy, {get lazy() {
delete this.lazy
return this.lazy = lazy();
}});
// Monad
const lazyChain = mx => fm =>
Lazy(() => fm(mx.lazy).lazy);
const lazyChain_ = fm => mx =>
Lazy(() => fm(mx.lazy).lazy);
const lazyOf = x => Lazy(() => x);
// mfix = chain_ => fm => chain_(fm) (mfix(chain_) (fm)) ???
// MAIN
const trace = x => (console.log(x), x);
const app_ = x => f => trace(f(x));
const readLine = s => Lazy(() => window.prompt(s));
const fac = x => x === 0 ? 1 : x * fac(x - 1),
tri = x => x === 0 ? 0 : x + tri(x - 1);
const foo = lazyChain(readLine("enter true/false")) (s => {
return lazyOf(s === "true"
? fac : tri);
});
const bar = x => lazyChain(foo) (y => lazyOf(app_(x) (y)));
const main = bar(5);
console.log("effect has not yet been unleashed..");
main.lazy;
How do I implement the Lazy
instance of MonadFix
in Javascript?
You have a representation of thunks with your
Lazy
construct, which happens to form a monad (where monadic application is just strict function application, as you’ve written), but you don’t have a representation ofIO
, so you’re essentially just usingunsafePerformIO
everywhere.In your model, each I/O action can only be executed once, so I expect this will not work quite the way you expect unless you add a separate
IO
wrapper with something like arunIO
function (or property, if you like) that does not shareLazy
’s behaviour of overwriting itself with the memoised result, or perhaps if you add some way to explicitly duplicate a deferred action. Of course,mfix f
only runs the effects off
once anyway—that is, it’s for using effects to produce a lazy/self-referential data structure—so this may be fine for your purposes.Those caveats aside, it’s perfectly possible to express something resembling an instance of
MonadFix
for your thunk structure, but it’s just a lazy version offix
:If you have side effects interleaved in these thunks, they’ll happen as you force them.
I think the
MVar
business in the original Haskell code is mainly just a convenient way to catch a subset of infinite loops, by ensuring that if the action tries to force the thunk before a value is written into it, then the runtime will be able to detect that the thread is “blocked indefinitely in anMVar
operation” and raise an error.And as a side note, you should be able to simplify your
lazyChain
definition to justfm(mx.lazy)
, sinceLazy(() => fm(mx.lazy).lazy)
forces the result and then immediately wraps it in a thunk again, which is both extra work and possibly too eager about your interleaved effects. Your version is effectivelylet fx = fm mx in mx `seq` fx `seq` fx
, which can be simplified (bya `seq` a
=a
) tomx `seq` fm mx
, which is then equivalent tofm $! mx
, i.e., strict function application.