操作真的与一个免费的monad同构吗?

时间:2013-01-10 17:08:04

标签: haskell monads

证明

this博文中,Tekmo指出我们可以证明ExitSuccess退出是因为(我认为)它就像那个构造函数的Const仿函数(它不带x fmap的行为与const}相似。

通过操作包,Tekmo的TeletypeF可能会被翻译成这样的内容:

data TeletypeI a where
    PutStrLn :: String -> TeletypeI ()
    GetLine :: TeletypeI String
    ExitSuccess :: TeletypeI ()

我已经读过,操作与免费monad是同构的,但我们可以在这里证明ExitSuccess退出吗?在我看来,它遇到了与exitSuccess :: IO ()完全相同的问题,特别是如果我们要为它编写一个解释器,我们需要把它写成就像它没有退出一样: / p>

eval (ExitSuccess :>>= _) = exitSuccess

与不涉及任何模式通配符的免费monad版本比较:

run (Free ExitSuccess) = exitSuccess

懒惰

Operational Monad Tutorial apfelmus中提到了一个缺点:

  

状态monad表示为s - > (a,s)可以应付一些无限的   像

这样的节目
evalState (sequence . repeat . state $ \s -> (s,s+1)) 0
     

而指令列表方法没有希望处理   因为只有最后一个Return指令才能返回值。

对于免费的monad也是如此吗?

3 个答案:

答案 0 :(得分:16)

(请允许我通过大胆结合以前的答案来获得大奖。; - ))

关键的观察是:证明究竟是什么Free TeletypeF方面的表述允许我们证明以下内容:

  

类型Free TeletypeF a的程序的每个解释器必须在遇到ExitSuccess指令时退出。换句话说,我们自动获得代数定律

 interpret (exitSuccess >>= k) = interpret exitSuccess

因此,Free monad实际上允许你将某些代数定律加入到该类型中。

相比之下,操作方法并不限制ExitSuccess的语义,没有与每个解释器相关的相关代数法。可以编写在遇到此指令时退出的解释器,但也可以编写不能解释的解释器。

当然,您可以通过检查证明任何特定的口译员满足法律,例如因为它使用通配符模式匹配。 Sjoerd Visscher观察到您还可以通过将ExitSuccess的返回类型更改为Void来在类型系统中强制执行此操作。但是,这不适用于可以融入自由单子的其他法律,例如mplus指令的分配法。

因此,如果你将“自由”解释为“最少量的代数法则”,那么在一系列令人困惑的事件中,操作方法比免费monad更自由。

这也意味着这些数据类型不是同构的。但是,它们是等价的:用Free编写的每个解释器都可以转换为用Program编写的解释器,反之亦然。

就我个人而言,我喜欢将我的所有法律都放入翻译中,因为有很多法律无法被融入免费的monad中,我喜欢将它们全部放在一个地方

答案 1 :(得分:11)

答案是肯定的,但前提是您使用TeletypeF的其他翻译:

data TeletypeI a where
    PutStrLn :: String -> TeletypeI ()
    GetLine :: TeletypeI String
    ExitSuccess :: TeletypeI Void

TeletypeI的参数是操作将/必须提供给程序其余部分的内容。它是

中延续k的参数类型
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 VoidIO a类型来修复。

答案 2 :(得分:5)

答案是否定的,你无法证明操作者忽略了exitSuccess上程序的其余部分。将TeletypeITeletypeF对比以了解原因。为了便于比较,我将在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终止。