【问题标题】:How to implement MonadFix instance of an IO-like type in JS?如何在 JS 中实现类 IO 类型的 MonadFix 实例?
【发布时间】:2021-03-22 08:38:46
【问题描述】:

我在尝试将以下 Haskell 代码转换为 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

就我们的小时间而言,让我们一行一行地走过这一段 旅行类比:

  1. 创建一个空的可变变量。
  2. 预测将包含在该可变变量中的未来值。
  3. 使用预测的未来值调用函数 f。
  4. 将 f 的结果存储在变量中,从而实现第 2 行所要求的预言。
  5. 返回该结果。

我拥有的是一个特殊的Lazy 类型,它处理(或者更确切地说是推迟)同步IO。但是,我想用值递归从直接递归中抽象出来:

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;

如何在Javascript中实现MonadFixLazy实例?

【问题讨论】:

    标签: javascript haskell monads monadfix


    【解决方案1】:

    您的 Lazy 构造具有 thunks 的表示形式,它恰好形成了一个单子(如您所写,单子应用程序只是严格的函数应用程序),但您没有没有IO 的表示,所以你基本上只是在任何地方使用unsafePerformIO

    在您的模型中,每个 I/O 操作只能执行一次,因此我希望这不会按您期望的方式工作,除非您添加一个单独的 IO 包装器,例如runIO 函数(或属性,如果你喜欢) 共享 Lazy 用记忆结果覆盖自身的行为,或者如果你添加某种方式来显式复制延迟操作.当然,mfix f 无论如何只运行一次 f 的效果——也就是说,它是为了使用效果来产生一个惰性/自引用数据结构——所以这可能适合你的目的.

    除了这些注意事项之外,完全可以为您的 thunk 结构表达类似于 MonadFix 实例的内容,但这只是 fix 的惰性版本:

    // cf. Haskell ‘fix f = let ans = f ans in ans’
    function lazyFix(f) {
      var ans = Lazy(() => f(ans));
      return ans;
    }
    

    如果您在这些 thunk 中交错了副作用,它们会在您强制它们时发生。

    我认为原始 Haskell 代码中的 MVar 业务主要只是捕获无限循环子集的一种方便方法,通过确保如果动作试图强制 thunk before 一个值写入其中,然后运行时将能够检测到线程“在MVar 操作中被无限期阻塞”并引发错误。

    附带说明一下,您应该能够将您的 lazyChain 定义简化为 fm(mx.lazy),因为 Lazy(() =&gt; fm(mx.lazy).lazy) 强制执行结果,然后立即将其再次包装在一个 thunk 中,这既是额外的工作,也可能太渴望你的交错效果。你的版本实际上是let fx = fm mx in mx `seq` fx `seq` fx,可以简化(通过a `seq` a = a)为mx `seq` fm mx,然后等价于fm $! mx,即严格函数应用。

    【讨论】:

    • 该代码中的MVar 有点“腰带和吊带”。当前实现使用unsafeDupableInterleaveIO 并依赖MVar 来保证线程安全。
    • 嗯...实际上,我认为在编写时,unsafeInterleaveIO 可能不是线程安全的?我不记得了。无论如何,现在事情已经很合理了......
    • 是的,每个.lazy 查找等同于unsafePerformIO。我仍然可以从Lazy 中受益,因为它延迟了效果,使它们可组合,而且我只在我的应用程序的边缘运行它们。还有另一种类型Defer 不支持共享。我了解到,当您使用不纯代码时,“积极”共享是一个很好的功能,b/c 它有助于实现非幂等效果。
    • JS 实际上不会评估Lazy(() =&gt; fm(mx.lazy).lazy) 中的thunk 文字() =&gt; fm(mx.lazy).lazy,但只有在查找.lazy 时才会评估它。如果我只有fm(mx.lazy),那就太急切了,因为fm 会立即被调用,结果是mx 的效果。
    猜你喜欢
    • 1970-01-01
    • 2016-11-05
    • 2012-06-19
    • 2020-06-11
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多