我正在尝试理解index-core
样式的索引monad。我陷入了一个悖论,即在构建一些例子之前我无法理解这些原则,在理解原理之前我无法构建例子。
我正在尝试构建一个索引状态monad。到目前为止,我的直觉告诉我它应该是这样的
type a :* b = forall i. (a i, b i)
newtype IState f a i = IState { runIState :: f i -> (a :* f) }
我可以通过设置f = Identity
并正确选择a
来恢复“受限制”状态monad:
type IState' s s' a = IState Identity (a := s') s
但我感到很失落。有人可以确认我的排名正确吗?
我使用a similar question on the indexed continuation monad作为指导,但我认为它不够接近。
答案 0 :(得分:4)
我们可以从链接的索引Cont
答案中复制Gabriel的论点。如果标准索引状态monad是
State s s' a = s -> (a, s')
然后我们分阶段推广它。首先,使用Identity
将具体类型s
和s'
反映为索引类型空间Identity
中的光纤。
State s s' a = s -> (a, s')
~ Identity s -> (a, Identity s')
然后通过将值类型a
概括为索引类型以及“目标”索引,类型为s'
。
~ Identity s -> (a , Identity s')
~ Identity s -> (a s', Identity s')
然后使用存在类型擦除目标索引。我们稍后会恢复它。
data a :* b = forall i . P (a i) (b i)
Identity s -> (a s', Identity s')
~ Identity s -> P a Identity
最后,我们会注意到Identity
是状态空间的索引类型,a
是值空间的索引类型,因此我们可以将IState
写为
newtype IState s -- indexed state space
a -- indexed value space
i -- index
= IState { runIState :: s i -> a :* s }
-- State { runState :: s -> (a, s) } for comparison
为什么要使用存在量化的对而不是普遍量化的对?第一个推动来自这样一个事实:与a
相关联的索引在IState
中正面发生,而在ICont
中则显示为负值。第二个提示来自写作returnI
。如果我们使用通用量化版本并尝试编写returnI
newtype IState' s a i = IState' { runIState' :: s i -> (forall i . (a i, s i)) }
returnI :: a i -> IState' s a i
returnI a = IState' (\si -> (?forget a, ?forget si))
我们需要这个forget
函数来忘记索引的所有信息。
但是,如果我们改为使用存在量化对,则由返回对的构造函数(即IState
值的实现者)来选择索引。这样,我们就可以实例化IFunctor
和IMonad
instance IFunctor (IState s) where
-- fmapI :: (a :-> b) -> IState s a :-> IState s b
fmapI phi (IState go) = IState $ \si ->
case go si of
P ax sx -> P (phi ax) sx
instance IMonad (IState s) where
-- returnI :: a :-> IState s a
return ai = IState (\si -> P ai si)
-- bindI :: (a :-> IState s b) -> (IState s a :-> IState s b)
bindI f m = IState $ \s ->
case runIState m s of
P ax sx -> runIState (f ax) sx
使用这种存在对的唯一缺点是它实际上很难使用。例如,我们真的希望能够使用“尖头”索引类型构造函数(:=)
来修复存在对的已知索引和项目。
one :: (a := i :* b) -> a
two :: (a := i :* b) -> b i
不幸的是,即使我们知道它是什么,Haskell也不够聪明,无法强迫存在主义,所以这些预测中的第二个具有令人讨厌的实现
one :: (a := i :* b) -> a
one (P (V a) _) = a
two :: (a := i :* b) -> b i
two (P _ s) = unsafeCoerce s
最后,证据在于布丁。我们可以使用IState
来实现我们习惯看到的有状态效果的标准补充。
-- Higher order unit type
data U1 a = U1
put :: s i -> IState s U1 j
put s = IState (\_ -> P U1 s)
get :: IState s s i
get = IState (\s -> P s s)
并使用它们来实现一些通用的高阶组合器,例如modify(需要一个显式的类型签名,但你可以通过一些思考手动实现计算)
modify :: (s :-> s) -> IState s U1 i
modify f = get ?>= put . f
然而,除了这些之外,我们还有其他方式来表示由于通过(:=)
的限制而更明确地建立索引的组合子。这可以用于传递有关索引的更多信息。
put' :: s i1 -> IState s (() := i1) i
put' s = IState (\_ -> P (V ()) s)
get' :: IState s (s i := i) i
get' = IState (\s -> P (V s) s)
modify' :: (s -> s) -> IState (s := j) (() := j) i
modify' f = get >>= put' . V . f
modify'' :: (s i -> s k) -> IState s (() := k) i
modify'' f = get' >>= put' . f
最后,我们可以使用所有这些来实现一个例子。例如,我们可以在文件句柄状态上构建索引类型,而不是它非常有用。
data Open
data Closed
data Any a
data St where
So :: Int -> St Open
Sc :: St Closed
Sa :: a -> St (Any a)
getAny :: St (Any a) -> a
getAny (Sa a) = a
然后我们可以建立
open :: String -> File Closed Open ()
open name = put' (SOpen $ getHandle name) where
getHandle = length
close :: File Open Closed ()
close = put' SClosed
getHandle :: File Open Open Int
getHandle = do
SOpen i <- get'
return i
putA :: a -> File x (Any a) ()
putA a = put' (SAny a)
,其中
open "foo" >> close -- typechecks
open "foo" >> close >> close -- fails
open "foo" >> getHandler >> close -- typechecks
open "foo" >> close >> getHandler -- fails
等等
> one $ runIState (do putA 4
sa <- get'
return (getAny sa)) Sc
4
> one $ runIState (do putA ()
sa <- get'
return (getAny sa)) Sc
()
> one $ runIState (do putA 4
putA ()
sa <- get'
return (getAny sa)) Sc
()
所有工作。