您要找的确实是 Atkey 的参数化单子 http://bentnib.org/paramnotions-jfp.pdf,现在更常被称为索引单子.
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
to j
进行计算,将其取自j
to k
,返回更大的计算i
to k
. ireturn
允许您将纯值提升到一元计算中,而不会更改类型级别状态。
我将使用索引自由单子捕获这种请求-响应操作的结构,主要是因为我不想弄清楚如何编写IMonad
我自己的类型的实例:
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
来自以下函子的 monad 免费:
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 ()))
You can 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
, Close
or Ring
返回一个值。所以我认为你真正需要的是以下更简单的类型对齐列表 type:
data 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
. Nil
说你总是可以找到从节点开始的路径i
对自己和Cons
提醒我们,千里之行始于足下:如果你有优势i
to j
和一条路径j
to k
,您可以将它们组合起来形成一条路径i
to k
。它被称为一个类型对齐列表因为一个元素的结束类型必须与下一个元素的起始类型匹配。
在咖喱霍华德街的另一边,如果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
符号(虽然我think RebindableSyntax
确实允许您重载列表文字),但是使用以下命令构建计算(.)
看起来像是纯函数的排序,我认为这对于你正在做的事情来说是一个很好的类比。对我来说,使用索引单子需要额外的脑力——一种稀有而珍贵的自然资源。当更简单的结构就可以时,最好避免 monad 的复杂性。