如何编码类型中可能的状态转换?

时间:2016-10-22 21:36:41

标签: haskell types idris

我试图在Haskell中复制这段Idris代码,该代码通过类型强制执行正确的动作排序:

 data DoorState = DoorClosed | DoorOpen
 data DoorCmd : Type ->
                DoorState ->
 DoorState ->
                Type where
      Open : DoorCmd     () DoorClosed DoorOpen
      Close : DoorCmd    () DoorOpen   DoorClosed
      RingBell : DoorCmd () DoorClosed DoorClosed
      Pure : ty -> DoorCmd ty state state
      (>>=) : DoorCmd a state1 state2 ->
              (a -> DoorCmd b state2 state3) ->
              DoorCmd b state1 state3

由于(>>=)运算符的重载,可以编写如下的monadic代码:

do Ring 
   Open 
   Close

但编译器拒绝不正确的转换,如:

do Ring
   Open 
   Ring
   Open

我试图在下面的Haskell片段中遵循这种模式:

 data DoorState = Closed | Opened

 data DoorCommand (begin :: DoorState) (end :: DoorState) a where
   Open  :: DoorCommand 'Closed 'Opened ()
   Close :: DoorCommand 'Opened 'Closed ()
   Ring  :: DoorCommand 'Closed 'Closed ()

   Pure  :: x -> DoorCommand b e x
   Bind  :: DoorCommand b e x -> (x -> DoorCommand e f y) -> DoorCommand b f y

 instance Functor (DoorCommand b e) where
   f `fmap` c = Bind c (\ x -> Pure (f x))

 -- instance Applicative (DoorCommand b e) where
 --    pure    = Pure
 --    f <*> x = Bind f (\ f' -> Bind x (\ x' -> Pure (f' x')))

 -- instance Monad (DoorCommand b e) where
 --   return = Pure
 --   (>>=) = Bind

但当然失败了:ApplicativeMonad实例无法正确定义,因为它们需要两个不同的实例才能正确排序操作。构造函数Bind可用于强制执行正确的排序,但我无法使用&#34;更好的&#34;做-符号。

我如何编写此代码以便能够使用do-notation,例如防止Command s的无效序列?

1 个答案:

答案 0 :(得分:8)

你正在寻找的确是Atkey的parameterised monad,现在更常被称为索引monad

class IFunctor f where
    imap :: (a -> b) -> f i j a -> f i j b
class IFunctor m => IMonad m where
    ireturn :: a -> m i i a
    (>>>=) :: m i j a -> (a -> m j k b) -> m i k b

IMonad是一类类似monad的事物m :: k -> k -> * -> *,用于描述属于k种类型的有向图的路径。 >>>=绑定计算,该计算将类型级状态从i转换为j到将jk转移到i的计算,返回更大的计算从kireturnIMonad允许您将纯值提升为不会改变类型级别状态的monadic计算。

我将使用索引的免费monad 来捕获此类请求 - 响应操作的结构,主要是因为我不想弄清楚如何为自己的类型编写data IFree f i j a where IReturn :: a -> IFree f i i a IFree :: f i j (IFree f j k a) -> IFree f i k a instance IFunctor f => IFunctor (IFree f) where imap f (IReturn x) = IReturn (f x) imap f (IFree ff) = IFree $ imap (imap f) ff instance IFunctor f => IMonad (IFree f) where ireturn = IReturn IReturn x >>>= f = f x IFree ff >>>= f = IFree $ imap (>>>= f) ff 实例:

Door

我们可以从以下仿函数中免费构建您的data DoorState = Opened | Closed data DoorF i j next where Open :: next -> DoorF Closed Opened next Close :: next -> DoorF Opened Closed next Ring :: next -> DoorF Closed Closed next instance IFunctor DoorF where imap f (Open x) = Open (f x) imap f (Close x) = Close (f x) imap f (Ring x) = Ring (f x) type Door = IFree DoorF open :: Door Closed Opened () open = IFree (Open (IReturn ())) close :: Door Opened Closed () close = IFree (Close (IReturn ())) ring :: Door Closed Closed () ring = IFree (Ring (IReturn ())) monad:

open

你可以close一扇门,导致当前关闭的门打开,ring当前打开的门,或RebindableSyntax门保持关闭,大概是因为这个房子的居住者并不想见到你。

最后,Monad语言扩展意味着我们可以使用自己的自定义IMonad替换标准(>>=) = (>>>=) m >> n = m >>>= const n return = ireturn fail = undefined door :: Door Open Open () door = do close ring open 类。

Open

但是我注意到你并没有真正使用monad的绑定结构。您的所有构建基块CloseRingdata Path g i j where Nil :: Path g i i Cons :: g i j -> Path g j k -> Path g i k 都不会返回任何值。所以我认为你真正需要的是以下更简单的类型对齐列表类型:

Path :: (k -> k -> *) -> k -> k -> *

在操作上,k就像一个链表,但它有一些额外的类型级结构,再次描述通过有节点在g中的有向图的路径。列表的元素是边Nili表示您始终可以找到从节点Cons到自身的路径,i提醒我们千里之行始于一步:如果您有一个优势{ {1}}到j以及从jk的路径,您可以将它们组合在一起,形成从ik的路径。它被称为类型对齐列表,因为一个元素的结束类型必须与下一个元素的起始类型匹配。

在Curry-Howard Street的另一边,如果g是二元逻辑关系,那么Path g构造其自反传递闭包。或者,Path g分类为图g免费类别中的态射类型。在自由类别中编写态射只是(翻转)附加类型对齐列表。

instance Category (Path g) where
    id = Nil
    xs . Nil = xs
    xs . Cons y ys = Cons y (xs . ys)

然后我们可以用Door

来写Path
data DoorAction i j where
    Open :: DoorAction Closed Opened
    Close :: DoorAction Opened Closed
    Ring :: DoorAction Closed Closed

type Door = Path DoorAction

open :: Door Closed Opened
open = Cons Open Nil
close :: Door Opened Closed
close = Cons Close Nil
ring :: Door Closed Closed
ring = Cons Ring Nil

door :: Door Open Open
door = open . ring . close

你没有得到do符号(虽然我认为 RebindableSyntax确实允许你重载列表文字),但用(.)构建计算看起来像纯函数的排序,我认为这对你正在做的事情来说是一个相当不错的类比。对我而言,它需要额外的智力 - 一种稀有而珍贵的自然资源 - 才能使用索引monad。当一个更简单的结构可以做到时,避免monad的复杂性会更好。