将IO作为MonadCont的实例是否有意义?

Ear*_*ine 3 monads haskell continuation

显然,MonadConts更受限制,并且比普通的Monads 更强大,这要归功于它callCC.这意味着它的实例越来越少,你可以用它做更多的事情.

当看的定义的实例MonadCont,它看起来像一切所列要求或者Cont或者ContT或者已经存在的MonadCont实例.这意味着我们必须开始与一些Cont或者ContT,特别是不能把IOMonadCont.

但是,我认为callCCIO上下文中使用是有意义的,因此我们可以简化以下内容(根据官方Hackage页面 callCC示例进行调整):

whatsYourName :: IO ()
whatsYourName = do
    name <- getLine
    let response = flip runCont id $ do
        callCC $ \exit -> do
            when (null name) (exit "You forgot to tell me your name!")
            return $ "Welcome, " ++ name ++ "!"            
    print response
Run Code Online (Sandbox Code Playgroud)

whatsYourName' :: IO ()
whatsYourName' = do
    name <- getLine
    response <- callCC $ \exit -> do
            when (null name) (exit "You forgot to tell me your name!")
            return $ "Welcome, " ++ name ++ "!"            
    print response
Run Code Online (Sandbox Code Playgroud)

callCC更干净的方式在do块中使用.

当然,为了使IO一个实例MonadCont我们必须有一些魔力,因为callCC对于IO"用未来计算调用给定函数指定在现实世界中接下来会发生什么",所以只有解释器或编译器才能真正知道这是什么意思.另一方面,我没有看到任何理论上的原因,这是可以导入的,因为Scheme已经有很长一段时间了,并且制作这样的实例根本不需要语言更改.

可能的问题

我能想到的一个因素是语义callCC与正确的清理保证相冲突.许多语言提供了"try ... finally"控件以进行适当的清理,而C++的析构函数也保证了这一点.我不确定它在Haskell中是什么,但是如果callCC可用于IO一个可以使用它来逃避任何IO需要清理的任何相关上下文,那么提供保证将变得不可能,因为你可以看到Ruby中发生了什么.

讨论意见

来自@jozefg的答案非常好.我只想在这里写下我的意见.

  1. 确实MonadCont来自mtl.但这并不意味着GHC或其他编译器无法定义unsafeCallCC和定义实例,如果MonadCont正确的定义在编译模块的范围内并且-XIOMonadCont正在设置.

  2. 我已经谈过异常安全问题,看起来很难确定.但是,在我看来,Haskell已经拥有unsafePerformIO,基本上甚至比不安全unsafeCallCC.

  3. callCC在大多数情况下,原因是太强大,应尽可能避免.但是,在我看来,延续传递样式可用于使惰性求值显式化,这有助于更好地理解程序,从而更容易找到可能的优化.因为CPS不是MonadCont,但是使用它并将深层嵌套的内部函数转换为符号是自然的步骤.

Dan*_*zer 9

我想这是一个坏主意.首先,MonadCont是在MTL中.GHC对此一无所知,这意味着让编译器依赖于第三方库,ick.

其次,callCC即使在一些非常高调的Schemers中也不受欢迎,主要是因为它使代码的推理成为一种痛苦!以同样的方式goto很难推理.特别是在Haskell,我们不得不担心

  1. 它是安全的吗?(这已经非常难了)
  2. 它是callCC安全的吗?

最后,我们甚至不需要它.如果你想使用continuation和IO,那么使用ContT IO它就完全一样强大了.但是,我几乎可以保证它可以用不那么强大的东西代替monad-prompt.延续是一把大锤,10次中的9次,callCC太强大了,使用更高阶函数和懒惰的组合可以更愉快地表达.

作为一个例子,其中一个原型用途callCC是实现类似异常的东西,但在Haskell中我们可以使用monads :)(它依赖于高阶函数和懒惰).

从本质上讲,你的建议增加了复杂性,意味着将MTL合并到基础中,以及一系列其他不愉快的东西,以避免liftIO.

RE编辑

  1. 当然你可以做到这一点,但那MonadCont当然不是一个实例:)
  2. 这有点不同,unsafePerformIO意在使用,以便其他人看不到副作用,因为你无法保证如何或何时执行.

    如果是这种情况callCCIO,那么你可以使用Cont!

  3. 延续传递风格很有用,我们拥有它ConT r IO!对于我来说,这是棺材中最大的钉子,我认为仅仅使用现有的库而不是一个困难且可能不安全的编译器黑客,这个想法没有任何好处.