![](/img/trans.png)
[英]Monad Stack Penetration Classes with Free/Operational Monad Transformers?
[英]Is operational really isomorphic to a free monad?
在这篇博文中,Tekmo指出我们可以证明ExitSuccess
退出是因为(我认为)它就像构造函数的Const
函子(它不带x
因此fmap
行为类似于const
)。
通过操作包,Tekmo的TeletypeF
可能会被翻译成如下:
data TeletypeI a where
PutStrLn :: String -> TeletypeI ()
GetLine :: TeletypeI String
ExitSuccess :: TeletypeI ()
我已经读过,操作与一个免费monad是同构的,但我们可以在这里证明ExitSuccess
退出吗? 在我看来它遇到了与exitSuccess :: IO ()
完全相同的问题,特别是如果我们要为它编写一个解释器,我们需要把它写成就像它没有退出一样:
eval (ExitSuccess :>>= _) = exitSuccess
与不涉及任何模式通配符的免费monad版本相比:
run (Free ExitSuccess) = exitSuccess
在操作Monad教程中, apfelmus提到了一个缺点:
状态monad表示为s - >(a,s)可以应对一些无限的程序,如
evalState (sequence . repeat . state $ \\s -> (s,s+1)) 0
而指令列表方法没有希望处理它,因为只有最后一个Return指令才能返回值。
对于免费的monad也是如此吗?
(请允许我通过大胆结合以前的答案来获得大奖。;-))
关键的观察是:证明究竟是什么? 根据Free TeletypeF
的表述,我们可以证明以下内容:
Free TeletypeF a
类型程序的每个解释器在遇到ExitSuccess
指令时必须退出。 换句话说,我们自动获得代数定律interpret (exitSuccess >>= k) = interpret exitSuccess
因此, Free
monad实际上允许您将某些代数定律烘焙到该类型中。
相反,操作方法不限制ExitSuccess
的语义,没有与每个解释器相关的相关代数法。 可以编写在遇到此指令时退出的解释器,但也可以编写不能解释的解释器。
当然,您可以通过检查证明任何特定的解释器满足法律,例如因为它使用通配符模式匹配。 Sjoerd Visscher观察到您还可以通过将ExitSuccess
的返回类型ExitSuccess
为Void
来在类型系统中强制执行此操作。 然而,这不适用于可以融入自由单子的其他法律,例如mplus
指令的分配法则。
因此,在一个令人困惑的事件转变中,如果你将“自由”解释为“最少量的代数定律”,那么操作方法比免费monad更自由。
这也意味着这些数据类型不是同构的。 但是,它们是等价的:每个用Free
编写的解释器都可以转换成用Program
编写的解释器,反之亦然。
就个人而言,我喜欢将我的所有法律都放入翻译中,因为有许多法律无法融入免费的monad中,我喜欢将它们全部放在一个地方。
答案是肯定的,但仅当您使用TeletypeF
的其他翻译时:
data TeletypeI a where
PutStrLn :: String -> TeletypeI ()
GetLine :: TeletypeI String
ExitSuccess :: TeletypeI Void
TeletypeI
的论点是操作将/必须提供给程序的其余部分。 它是继续k
in的参数的类型
eval (ExitSuccess :>>= k) = ...
由于没有Void
类型的值,我们可以确定永远不会调用k
。 (一如既往,我们必须忽略undefined
。)
等效类型是:
data TeletypeI a where
PutStrLn :: String -> TeletypeI ()
GetLine :: TeletypeI String
ExitSuccess :: TeletypeI a
现在我们必须为k
匹配任何类型提供一个值,我们也不能这样做。 这可能更实用,因为singleton ExitSuccess
现在具有灵活类型Program TeletypeI a
。
类似地, exitSuccess
可以通过给它类型IO Void
或IO a
来修复。
答案是否定的,你无法证明操作者忽略了exitSuccess
上的其余程序。 将TeletypeI
与TeletypeF
对比以了解原因。 为了便于比较,我将用GADT表示法重写TeletypeF
data TeletypeF x where | data TeletypeI x where
PutStrLn :: String -> x -> TeletypeF x | PutStrLn :: String -> TeletypeI ()
GetLine :: (String -> x) -> TeletypeF x | GetLine :: TeletypeI String
ExitSuccess :: TeletypeF x | ExitSuccess :: TeletypeI ()
使用TeletypeF
,我们可以立即构建实际程序:
GetLine (\str -> PutStrLn (map toUpper str) ExitSuccess)
TeletypeI
不拿出一个办法来指代“节目的其余部分”,因为同样的方式TeletypeF
一样。
-- TeletypeF:
GetLine (\str -> "rest of program" goes here)
-- or
PutStrLn someString ("rest of program" goes here)
-- or
ExitSuccess -- there is no "rest of program" slot provided
由于TeletypeI
缺少此“程序的其余部分”信息,因此当您遇到ExitSuccess
时,您将无法获得任何知识。
-- TeletypeI
PutStrLn someString -- no information about "rest of program"
-- or
GetLine -- no information about "rest of program"
-- or
ExitSuccess -- no information about "rest of program"
允许来的是“程序的其余部分”完全取决于Program
类型,它对应用它的指令集一无所知。 它只允许您将指令绑定在一起,并通过Return
终止。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.