【问题标题】:What is the underlying idea of chainRec?chainRec 的基本思想是什么?
【发布时间】:2018-11-06 04:23:25
【问题描述】:

[编辑]

这是How to implement a stack-safe chainRec operator for the continuation monad? 的后续问题


给出的是chainRec的类型

chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m b

通常,chainRec 与蹦床一起实现,以允许在 monad 中进行堆栈安全递归。但是,如果我们放下蹦床,我们可以为普通函数实现chainRec的类型,如下所示:

const chainRec = f => x => join(f(chainRec(f), of, x));

接下来,我想将它应用到递归动作中:

const map = f => g => x => f(g(x));
const join = f => x => f(x) (x);
const of = x => y => x;

const chainRec = f => x => join(f(chainRec(f), of, x));

const repeat = n => f => x => 
  chainRec((loop, done, args) =>
    args[0] === 0
      ? done(args[1])
      : loop([args[0] - 1, map(f) (args[1])])) ([n, of(x)]);

const inc = x => of(x + 1);

repeat(10) (inc) (0) (); // error

我想既然chainRec的定义中有一个join,那么repeat的实现中肯定有一个map,所以有两个嵌套的函数上下文可以折叠。然而它不起作用,我不知道如何解决它。

【问题讨论】:

  • 您是在询问chainRec 的基本思想还是您的具体问题?
  • "通常chainRec 与蹦床一起实现,以允许在 monad 中进行堆栈安全递归。" - 是的,那就是 what the c in the type is all about。 “但是,如果我们放弃蹦床......”那么你根本不应该实现chainRec,而应该简单地实现chain
  • 我想了解的是,如果chainRec 本质上是用mapjoin 实现的chain。由于chain 现在包含两个步骤,我们可以将它们分开。映射发生在fchainRec)和joinchainRec 本身内。
  • 然后我试图通过一个人为的例子来验证这个模糊的想法,但没有成功。
  • 如果没有蹦床,它只会增加意外类型错误的机会,混合 monad 类型和 c 类型。我认为这种形式只有助于理解递归是如何工作的。其背后的实际想法最好用chainRec = f => x => unwrapRecAndChain(inLazyRecWrapper(chainRec(f)), stopUnwrap, x))表达。

标签: javascript recursion functional-programming monads


【解决方案1】:

不知道你的 repeat 函数是做什么的,我猜你的调用 repeat(10)(inc)(0) 应该扩展到

map(inc)(
 map(inc)(
  map(inc)(
   map(inc)(
    map(inc)(
     map(inc)(
      map(inc)(
       map(inc)(
        map(inc)(
         map(inc)(
          of(0)
         )
        )
       )
      )
     )
    )
   )
  )
 )
)

由于您的 inc 出于某种原因返回函数 _ => Int 而不是普通的 Int,这将在函数 x 上调用 x + 1,从而导致该函数的字符串化 (y => x变成"y => x1"),尝试调用时会抛出异常。


修复const inc = x => x + 1; 后,您的repeat 功能仍然不起作用。它需要是简单的递归,与

const id = x => x
// rec :: ((a -> c, b -> c, a) -> c) -> a -> b
// here with c == b, no trampoline
const rec = f => x => f(rec(f), id, x) // a bit like the y combinator

const repeat = n => f => x => 
  rec((loop, done, [m, g]) =>
    m === 0
      ? done(g)
      : loop([m - 1, map(f)(g)])
  )([n, of(x)]);

repeat(10)(inc)(0)() // 10 - works!

根本不涉及单子!

如果我们想使用chainRec,我们需要引入一些任意的monad(这里:函数monad),并且fchainRec的回调需要返回那个monad类型的实例而不是只是loop/done:

chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m b
//                                                ^

我们可以通过简单地将返回值包装在 of 中来做到这一点:

const repeat = n => f => x => 
  chainRec((loop, done, [m, g]) =>
    of(m === 0
//  ^^
      ? done(g)
      : loop([m - 1, map(f)(g)])
     )
  )([n, of(x)]);

当然现在得到一个m b,也就是说,所有东西都包含在另一个函数中:

repeat(10)(inc)(0)()() // 10
//                  ^^

// repeat(1)(inc)(0) expands to `of(map(inc)(of(0)))

但我怀疑这是你想要的。

【讨论】:

  • 是的,这正是我想要的!我的错误是将inc 定义为一个动作,它与map 一起提供了嵌套的函数上下文。显然chainRecf 必须提供此上下文。
  • repeat 这样定义显然是无稽之谈,当然。下次我应该花更多的时间找一个不那么做作的例子。
  • 为了避免泄露嵌套上下文,我实现了chainRec = f => x => join(f(chainRec(f), map(join) (of), x))
  • @ftor 我想您可以将inc 保留为一个动作,并简单地使用chain 而不是map 构建您的调用序列。尽管chainRec 仍然没有任何用处,因为您永远不会tail-链接递归调用
  • @ftor chainRec = f => x => join(f(chainRec(f), map(join) (of), x)) 的类型有误,不过
猜你喜欢
  • 2012-05-17
  • 1970-01-01
  • 2011-04-10
  • 1970-01-01
  • 2011-07-14
  • 2015-01-21
  • 2012-06-26
  • 2021-03-06
  • 1970-01-01
相关资源
最近更新 更多