如何在种类的单子中重复单子指令?

Posted

技术标签:

【中文标题】如何在种类的单子中重复单子指令?【英文标题】:how can I repeat monadic instructions inside kind's monads? 【发布时间】:2021-08-13 02:35:42 【问题描述】:

我知道我可以在 Kind 语言的 monad 中按顺序运行 monadic 指令,如下所示:

Test: _
  IO 
   IO.print(Nat.show(2))
   IO.print(Nat.show(3))
   IO.print(Nat.show(4))
  

output:
2
3
4

但是是否可以像下面这样重复运行一元指令?

Test: _
  a = [2,1,3,4,5]
  IO 
    for num in a:
      IO.print(Nat.show(num))
  

如果可能的话,我该如何正确地做到这一点?

【问题讨论】:

我不知道 kind,但它提供递归吗? @Bergi 是的,它是一种功能性“校对”语言。 【参考方案1】:

Monad 通常只由两个运算符表示:

  return :: a -> m(a) // that encapulapse the value inside a effectful monad
  >>= :: m a -> (a -> m b) -> m b
  // the monadic laws are omitted

注意,bind 操作符自然是递归的,一旦它可以组合两个 monad 甚至丢弃一个的值,返回可以被认为是一个“基本情况”。

m >>= (\a -> ... >>= (\b -> ~ i have a and b, compose or discard? ~) >>= fixpoint)

您只需要生成该序列,这非常简单。例如,在 Kind 中,我们将 monad 表示为一对,它接受一个 type-for-type 值并封装一个多态类型。

type Monad <M: Type -> Type> 
  new(
    bind: <A: Type, B: Type> M<A> -> (A -> M<B>) -> M<B>
    pure: <A: Type> A -> M<A>
  )

在你的例子中,我们只需要触发效果并丢弃值,递归定义就足够了:

action (x : List<String>): IO(Unit)
  case x 
    nil : IO.end!(Unit.new) // base case but we are not worried about values here, just the effects
    cons : IO 
      IO.print(x.head) // print and discard the value
      action(x.tail) // fixpoint
    
  

test : IO(Unit)
  IO 
    let ls = ["2", "1", "3", "4", "5"]
    action(ls)
  

您所知道的 IO 将通过一系列绑定来脱糖! 通常在 list 的情况下,它可以像 haskell 库的 mapM 函数一样泛化:

Monadic.forM(A : Type -> Type, B : Type,
  C : Type, m : Monad<A>, b : A(C), f : B -> A(C), x : List<A(B)>): A(C)
  case x 
    nil : b
    cons : 
      open m
      let k = App.Kaelin.App.mapM!!!(m, b, f, x.tail)
      let ac = m.bind!!(x.head, f)
      m.bind!!(ac, (c) k) // the >> operator
   

它自然会丢弃价值,最后我们可以做到:

action2 (ls : List<String>): IO(Unit)
  let ls = [IO.end!(2), IO.end!(1), IO.end!(3), IO.end!(4), IO.end!(5)]
  Monadic.forM!!!(IO.monad, IO.end!(Unit.new), (b) IO.print(Nat.show(b)), ls)

所以,action2 执行相同的操作,但在一行中!。 当您需要组合可以表示为单子折叠的值时:

Monadic.foldM(A : Type -> Type, B : Type,
  C : Type, m : Monad<A>, b : A(C), f : B -> C -> A(C), x : List<A(B)>): A(C)
  case x 
    nil : b
    cons : 
      open m
      let k = Monadic.foldM!!!(m, b, f, x.tail)
      m.bind!!(x.head, (b) m.bind!!(k, (c) f(b, c)))
   

例如,假设您想在一个循环中对您要求用户的一系列数字求和,您只需调用 foldM 并使用一个简单的函数进行组合:

Monad.action3 : IO(Nat)
  let ls = [IO.get_line, IO.get_line, IO.get_line]
  Monadic.foldM!!!(IO.monad, IO.end!(0), 
      (b, c) IO 
        IO.end!(Nat.add(Nat.read(b), c))
      ,
   ls)

test : IO(Unit)
  IO 
    get total = action3 
    IO.print(Nat.show(total))
  

目前,Kind 不支持 typeclass,因此它使事情变得更加冗长,但我认为将来可以考虑对 forM 循环语法的新支持。我们希望如此:)

【讨论】:

感谢这个非常完整的答案!如果在基本目录中有 foldMforM(可能还有 forM 的语法),那就太好了。

以上是关于如何在种类的单子中重复单子指令?的主要内容,如果未能解决你的问题,请参考以下文章

场景代码题:有200个骑手都想要抢这⼀个外卖单子,如何保证只有一个骑手接到单子?

场景代码题:有200个骑手都想要抢这⼀个外卖单子,如何保证只有一个骑手接到单子?

如何将列表单子函数转换为广度优先搜索?

如何使用猫和状态单子

如何对非单子使用`bound`?

单子设计模式(Singleton pattern)