用于概括索引的monad的类型级别的类半体式运算?

时间:2019-07-15 13:01:19

标签: haskell monads type-families type-level-computation

为了激发这个问题,让我们首先回顾一下沼泽标准的Hoare-Dijkstra风格的索引monad,以及索引作家monad的示例实例。

对于有索引的monad,我们只需要在(i)bind s上进行类型对齐:

class IMonadHoare m where
    ireturn :: a -> m i i a
    ibind :: m i j a -> (a -> m j k b) -> m i k b

然后为了证明这是可用的,让我们实现一个索引编写器monad:

import Prelude hiding (id, (.))
import Control.Category

newtype IWriter cat i j a = IWriter{ runIWriter :: (a, cat i j) }

instance (Category cat) => IMonadHoare (IWriter cat) where
    ireturn x = IWriter (x, id)

    ibind (IWriter (x, f)) k = IWriter $
        let (y, g) = runIWriter (k x)
        in (y, g . f)

它确实是一个类似作家的单子,因为我们可以实现通常的方法:

itell :: (Category cat) => cat i j -> IWriter cat i j ()
itell f = IWriter ((), f)

ilisten :: (Category cat) => IWriter cat i j a -> IWriter cat i j (a, cat i j)
ilisten w = IWriter $
    let (x, f) = runIWriter w
    in ((x, f), f)

ipass :: (Category cat) => IWriter cat i j (a, cat i j -> cat i j) -> IWriter cat i j a
ipass w = IWriter $
    let ((x, censor), f) = runIWriter w
    in (x, censor f)

好的,到目前为止很好。但是现在我想将其推广到其他类型的索引(heh)。我认为简单地为类型级别的monoid操作添加关联的类型族将是可行的,就像这样:

{-# LANGUAGE TypeFamilies, PolyKinds, MultiParamTypeClasses, FunctionalDependencies #-}

import Data.Kind

class IMonadTF idx (m :: idx -> Type -> Type) | m -> idx where
    type Empty m :: idx
    type Append m (i :: idx) (j :: idx) :: idx

    ireturn :: a -> m (Empty m) a
    ibind :: m i a -> (a -> m j b) -> m (Append m i j) b

那么,这行得通吗?好吧,您可以使用它来定义Empty / Append未被索引的内容,例如:

{-# LANGUAGE DataKinds, TypeOperators #-}

import GHC.TypeLists

newtype Counter (n :: Nat) a = Counter{ runCounter :: a }

instance IMonadTF Nat Counter where
    type Empty Counter = 0
    type Append Counter n m = n + m

    ireturn = Counter
    ibind (Counter x) k = Counter . runCounter $ k x

tick :: Counter 1 ()
tick = Counter ()

但是现在我们可以重新创建我们的IWriter示例了吗?不幸的是,我无法这么做。

首先,我们甚至不能声明Empty

data IWriter cat (ij :: (Type, Type)) a where
    IWriter :: { runIWriter :: (a, cat i j) } -> IWriter cat '(i, j) a

instance (Category cat) => IMonadTF (Type, Type) (IWriter cat) where
    type Empty (IWriter cat) = '(i, i)

这当然会失败,因为类型变量i不在范围内。

让我们将Empty更改为Constraint,然后重新创建Counter实例以进行验证:

class IMonadConstraint idx (m :: idx -> Type -> Type) | m -> idx where
    type IsEmpty m (i :: idx) :: Constraint
    type Append m (i :: idx) (j :: idx) :: idx

    ireturn :: (IsEmpty m i) => a -> m i a
    ibind :: m i a -> (a -> m j b) -> m (Append m i j) b

newtype Counter (n :: Nat) a = Counter{ runCounter :: a }

instance IMonadConstraint Nat Counter where
    type IsEmpty Counter n = n ~ 0
    type Append Counter n m = n + m

    ireturn = Counter
    ibind (Counter x) k = Counter . runCounter $ k x

tick :: Counter 1 ()
tick = Counter ()

现在我们至少可以写下IsEmpty (Writer cat)的定义,但是在下面的代码中,ireturn仍然没有进行类型检查。好像没有使用IsEmpty的定义来解决约束:

instance (Category cat) => IMonadConstraint (Type, Type) (IWriter cat) where
    type IsEmpty (IWriter cat) '(i, j) = i ~ j

    ireturn x = IWriter (x, id)
  

无法从上下文i ~ '(j0, j0)推论IsEmpty (IWriter cat) i

类似地,我们可以尝试通过添加附加约束来在中间强制对齐:

class IMonadConstraint2 idx (m :: idx -> Type -> Type) | m -> idx where
    type IsAppend m (i :: idx) (j :: idx) :: Constraint
    type Append m (i :: idx) (j :: idx) :: idx

    ireturn :: (IsEmpty m i) => a -> m i a    ibind :: (IsAppend m i j) => m i a -> (a -> m j b) -> m (Append m i j) b

但是对于IWriter来说,失败的方式是类似的:

instance (Category cat) => IMonadConstraint2 (Type, Type) (IWriter cat) where
    type IsAppend (IWriter cat) '(i, j) '(j', k) = j ~ j'
    type Append (IWriter cat) '(i, j) '(j', k) = '(i, k)

    ibind (IWriter (x, w)) k = IWriter $
        let (y, w') = runIWriter (k x)
        in (y, w' . w)
  

无法推断j ~ '(j1, j0)         来自上下文IsAppend (IWriter cat) i j

是因为IsEmptyIsAppend定义为“逐点”吗?

2 个答案:

答案 0 :(得分:4)

tl; dr:看起来您正在寻找按类别索引的单子。

可编译依据:https://gist.github.com/Lysxia/04039e4ca6f7a3740281e4e3583ae971


IMonadHoare不等同于IMonadTF(又称分级monad,请参阅effect-monad)。

尤其是,使用IMonadTF(分级monad)可以绑定任何两个计算,它们的索引被附加在一起,而使用IMonadHoare(索引monads)则只能绑定具有匹配索引的计算。因此,您不能轻易将任意IMonadHoare(例如IWriter)编码为IMonadTF,因为当索引不匹配时,bind没有意义。 / p>

IMonadHoare的这种“部分定义的组合”让人联想到类别,的确,我们可以使用由类别箭头标记的monad来概括IMonadHoareIMonadTF成对的索引或元素对。实际上,我们可以在两个类中看到类别的示例:

  1. (i, j)可以视为类别为ij为对象的箭头(因此i和{{1}之间恰好有一个箭头},一对j,虽然实际上并不重要,只存在一个);
  2. Monoid是类别。

这是按类别(i, j)索引的单子类别;此类通过与您的c :: k -> k -> Typec相对应的关联类型IdCat包括类别Empty的定义。它看起来实际上与Append几乎一样,除了您拥有类别IMonadTF而不是类半体c :: k -> k -> Type

idx :: Type

这是我前面提到的对的类别。在每个对象{-# LANGUAGE RankNTypes, TypeFamilies, PolyKinds, DataKinds #-} import Control.Category as C import Data.Kind class CatMonad (m :: forall (x :: k) (y :: k). c x y -> Type -> Type) where type Id m :: c x x type Cat m (f :: c x y) (g :: c y z) :: c x z xpure :: a -> m (Id m) a xbind :: m f a -> (a -> m g b) -> m (Cat m f g) b i(在某些集合/类型j中)之间,有一个箭头k(名称无关紧要,只有一个箭头)。也可以将其可视化为E中具有顶点的完整图形。

k

我们现在可以将data Edge (i :: k) (j :: k) = E 定义为IWriter。有点挑剔,您必须显式地放置CatMonadi,或者将它们量化到j实例头的错误位置。否则,不会有太多麻烦。完全不依赖CatMonad, 它只是其类型的占位符,其中包含重要的Ei索引。

j

答案 1 :(得分:1)

(没有理由将元组放入IWriter中;我将只用它

data IWriter (cat :: idx -> idx -> Type) (p :: (idx, idx)) (a :: Type) where
  IWriter :: a -> cat i j -> IWriter cat '(i, j) a

你写了

ireturn x = IWriter x id

适用于该类的所有版本。但是,IWriter x id :: forall i. IWriter cat (i, i) a虽然需要IWriter cat m a(其中catmaireturn的参数)。 (,) _ _不是m,句号。您也不能编写证明这一点的约束,因为然后i必须是ireturn的参数,但这是一个类型类方法,因此是不允许的。除此之外,正确的IMonad实际上是最后一个(IMonadConstraint1在一起的2)。

class IMonad (m :: idx -> Type -> Type) | m -> idx where
  type IsEmpty m (i :: idx) :: Constraint
  type IsAppend m (i :: idx) (j :: idx) :: Constraint
  type Append m (i :: idx) (j :: idx) :: idx
  ireturn :: IsEmpty m i => a -> m i a
  ibind :: IsAppend m i j => m i a -> (a -> m j b) -> m (Append m i j) b

您需要声明一个公理:

data IsTup (p :: (i, j)) where IsTup :: IsTup '(x, y)
isTup :: forall p. IsTup p
isTup = unsafeCoerce IsTup

forall (p :: (i, j)). exists (x :: i) (y :: j). p ~ '(x, y)语句在Haskell中既不可证明也不不可证明,因此如果需要,我们可以将其作为这样的公理。似乎足够“真实”。

instance Category cat => IMonad (IWriter cat) where
  type IsEmpty (IWriter cat) '(i, j) = i ~ j
  type IsAppend (IWriter cat) '(_, i) '(j, _) = i ~ j
  type Append (IWriter cat) '(i, _) '(_, j) = '(i, j)
  ireturn :: forall i a. IsEmpty (IWriter cat) i => a -> IWriter cat i a
  ireturn x | IsTup <- isTup @i = IWriter x id
  ibind (IWriter x w) f | IWriter y w' <- f x = IWriter y (w >>> w')
  -- IWriter :: forall cat p a. forall i j. p ~ '(i, j) => a -> cat i j -> IWriter cat p a
  -- IsTup   :: forall     p  . forall x y. p ~ '(x, y) =>                 IsTup       p
  -- in ibind, the two matches on IWriter prove that the two type-level tuple
  -- arguments are actually tuples
  -- in ireturn, you need to split that behavior out into it's own type IsTup,
  -- make forall p. IsTup p an axiom, and use it to show that the argument i
  -- is also really a tuple
相关问题