如何使用Agda实现分隔延续?

时间:2014-01-04 15:40:50

标签: standard-library agda delimited-continuations

我们可以很容易地在Agda中实现一个分隔的延续monad。

然而,由于Agda“标准库”具有an implementation of a delimited continuation monad,因此没有必要。但是,让我对这个实现感到困惑的是,在DCont类型中增加了一个额外的参数。

DCont : ∀ {i f} {I : Set i} → (I → Set f) → IFun I f
DCont K = DContT K Identity

我的问题是:为什么额外的参数K存在?我将如何使用DContIMonadDCont实例?我可以open以这种方式获得类似于(全局)范围中的以下参考实现的内容吗?

我尝试使用它的所有尝试都导致无法解决的问题。


使用Agda“标准库”参考实现分隔连续而不是

DCont        : Set → Set → Set → Set
DCont r i a  = (a → i) → r

return       : ∀ {r a} → a → DCont r r a
return x     = λ k → k x

_>>=_        : ∀ {r i j a b} → DCont r i a → (a → DCont i j b) → DCont r j b
c >>= f      = λ k → c (λ x → f x k)

join         : ∀ {r i j a} → DCont r i (DCont i j a) → DCont r j a
join c       = c >>= id

shift        : ∀ {r o i j a} → ((a → DCont i i o) → DCont r j j) → DCont r o a
shift f      = λ k → f (λ x → λ k′ → k′ (k x)) id

reset        : ∀ {r i a} → DCont a i i → DCont r r a
reset a      = λ k → k (a id)

1 个答案:

答案 0 :(得分:5)

让我先回答你的第二和第三个问题。查看DContT的定义方式:

DContT K M r₂ r₁ a = (a → M (K r₁)) → M (K r₂)

我们可以通过指定M = idK = id来恢复请求的定义(M也必须是monad,但我们有Identity monad)。 DCont已将M修正为id,因此我们留下K

import Category.Monad.Continuation as Cont

open import Function

DCont : Set → Set → Set → Set
DCont = Cont.DCont id

现在,我们可以打开RawIMonadDCont模块,前提是我们有相应记录的实例。幸运的是,我们这样做:Category.Monad.Continuation有一个名为DContIMonadDCont的记录。

module ContM {ℓ} =
  Cont.RawIMonadDCont (Cont.DContIMonadDCont {f = ℓ} id)

就是这样。让我们确保所需的操作确实存在:

return : ∀ {r a} → a → DCont r r a
return = ContM.return

_>>=_ : ∀ {r i j a b} → DCont r i a → (a → DCont i j b) → DCont r j b
_>>=_ = ContM._>>=_

join : ∀ {r i j a} → DCont r i (DCont i j a) → DCont r j a
join = ContM.join

shift : ∀ {r o i j a} → ((a → DCont i i o) → DCont r j j) → DCont r o a
shift = ContM.shift

reset : ∀ {r i a} → DCont a i i → DCont r r a
reset = ContM.reset

事实上,这个类型检查。您还可以检查实现是否匹配。例如,在C-c C-n上使用shift(规范化),我们得到:

λ {.r} {.o} {.i} {.j} {.a} e k → e (λ a f → f (k a)) (λ x → x)

模数重命名和一些隐式参数,这正是你问题中shift的实现。


现在是第一个问题。额外的参数是允许额外依赖索引。我没有以这种方式使用分隔的延续,所以让我在其他地方找到一个例子。考虑这个索引编写者:

open import Data.Product

IWriter : {I : Set} (K : I → I → Set) (i j : I) → Set → Set
IWriter K i j A = A × K i j

如果我们有某种索引的monoid,我们可以为IWriter编写一个monad实例:

record IMonoid {I : Set} (K : I → I → Set) : Set where
  field
    ε   : ∀ {i} → K i i
    _∙_ : ∀ {i j k} → K i j → K j k → K i k

module IWriterMonad {I} {K : I → I → Set} (mon : IMonoid K) where
  open IMonoid mon

  return : ∀ {A} {i : I} →
    A → IWriter K i i A
  return a = a , ε

  _>>=_ : ∀ {A B} {i j k : I} →
    IWriter K i j A → (A → IWriter K j k B) → IWriter K i k B
  (a , w₁) >>= f with f a
  ... | (b , w₂) = b , w₁ ∙ w₂

现在,这有用吗?想象一下,您想使用编写器生成消息日志或类似的东西。通常无聊的清单,这不是问题;但是如果你想使用矢量,你会陷入困境。如何表达该类型的日志可以改变?使用索引版本,您可以执行以下操作:

open import Data.Nat
open import Data.Unit
open import Data.Vec
  hiding (_>>=_)
open import Function

K : ℕ → ℕ → Set
K i j = Vec ℕ i → Vec ℕ j

K-m : IMonoid K
K-m = record
  { ε   = id
  ; _∙_ = λ f g → g ∘ f
  }

open IWriterMonad K-m

tell : ∀ {i j} → Vec ℕ i → IWriter K j (i + j) ⊤
tell v = _ , _++_ v

test : ∀ {i} → IWriter K i (5 + i) ⊤
test =
  tell [] >>= λ _ →
  tell (4 ∷ 5 ∷ []) >>= λ _ →
  tell (1 ∷ 2 ∷ 3 ∷ [])

嗯,这是很多(ad-hoc)代码来说明问题。我没有多想,所以我很确定有更好/更有原则的方法,但它说明了这种依赖性可以让你的代码更具表现力。

现在,您可以将相同的内容应用于DCont,例如:

test : Cont.DCont (Vec ℕ) 2 3 ℕ
test c = tail (c 2)

如果我们应用定义,则类型会缩减为(ℕ → Vec ℕ 3) → Vec ℕ 2。我知道,不是很有说服力的例子。但也许你现在可以知道这个参数的作用了,这可能会更有用。