IO单子的界定

18

在Safe Haskell中,至少从函数签名中可以知道某个函数是否执行IO操作,但是IO包括很多不同的东西——putStr、数据库访问、文件读写、IORefs等。

如果我将类型签名用作运行任意代码的安全措施,那么也许我愿意接受一些IO操作——例如putStr等,但是不愿意接受其他操作。

是否有一种方法可以定义一个受限制的IO单子,只能执行一小部分正常的IO操作?如果可以的话,举个例子(例如使用putStr)将非常受欢迎!


2
这是我一段时间前发布的一个Reddit帖子,关于这个确切的问题。那里有很好的讨论和示例代码,你可以用它作为实现的基础。 - bheklilr
2个回答

24

跟进我的评论,你可以使用类似的方法自己实现:

class Monad io => Stdout io where
    putStr_ :: String -> io ()
    putStrLn_ :: String -> io ()
    print_ :: Show a => a -> io ()
    -- etc

instance Stdout IO where
    putStr_ = putStr
    putStrLn_ putStrLn
    print_ = print

myFunc :: Stdout io => io ()
myFunc = do
    val <- someAction
    print_ val
    let newVal = doSomething val
    print_ newVal

main :: IO ()
main = myFunc

这将完全没有任何运行时开销,因为 GHC 将优化这些 typeclasses 仅使用 IO monad,它是可扩展的,易于编写,并且可以轻松地与 monad 转换和 MonadIO 类相结合。如果您有多个类,例如具有定义了 getLine_getChar_ 等的 Stdin 类,您甚至可以将这些 typeclasses 结合起来。

class (Stdout io, Stdin io) => StdOutIn io where

myFunc :: StdOutIn io => io ()
myFunc = do
    val <- getLine_
    putStrLn_ $ "Echo: " ++ val

main :: IO ()
main = myFunc

3
+1 因为它能够良好组合。你可以构建小型类型类来处理Stdout(标准输出),文件访问,网络,导弹发射等任何内容,然后任意、流畅地组合它们。 - daniel gratzer
这正是我想要的! - user2141650
3
通过使用-XConstraintKinds,您可以编写type StdOutIn io = (Stdout io, Stdin io)以实现类似的效果。 - Alex R
@AlexR,使用约束种类是否比我展示的方式更有好处?我认为对于这种情况来说,效果是相同的,因为这些类非常简单。这是一种替代语法,但我不认为我会从该扩展中获得任何功能价值。不过我可能错了,我并不自诩为专家。 - bheklilr
我想不出有什么直接的方法,但可以看看constraints包。 - Alex R

10

只需定义一个围绕IO a的新类型,并使用Monad实例定义预批准函数的封装版本,不要导出构造函数,以便仅可以在monad中使用您封装的函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接