haskell - 如何创建有限版本的 IO monad

标签 haskell monad-transformers

我使用 monad 转换器堆栈编写了许多函数:

data Options
data Result
data Input
type Ingest a = EitherT String (ReaderT Options IO) a

foo :: Input -> Ingest Result

等等。现在,这些函数中的大多数基本上都是纯粹的。我只需要在其中一个函数中使用 IO:此函数读取一个文件,并记录(使用 log :: String -> IO () )它已经这样做了。所以这个函数的杂质“感染”了我的整个代码库,使得所有这些函数都能够执行 IO,即使它们不需要,除了调用这个函数。这是令人反感的,原因有两个:
  • 不清楚这些可能执行的有限 IO 子集
  • 不清楚哪些函数实际执行该 IO

  • 一位同事建议参数化 Ingest在我喜欢的基本单子(monad)类型上。具体来说,定义一个用于读取文件内容的类型类,并为 IO 以及可能为其他一些用于编写测试的 monad 提供一个实例:
    class Monad m => ReadFile m where
      readFile :: FilePath -> m Text
    
    instance ReadFile IO where
      readFile = Data.Text.IO.readFile
    

    用于记录的类型类已经存在,所以我可以使用现有的类型类。但是后来我不确定如何使用这个新类。首先,我用什么替换我的类型别名?我不能写
    type Ingest m a = (Logging m, ReadFile m) => EitherT String (ReaderT Options m) a
    

    因为不允许对类型同义词进行约束。我必须将该约束添加到我的每个函数中吗?原则上这很好,因为它标记了可能需要读取文件的函数,但实际上这些函数是相互递归的,因此它们都需要该权限,因此将它们全部写出来很痛苦。

    我可以定义一个新类型包装器而不是类型同义词,但我认为这不会让事情变得更好:我仍然必须将这个新约束添加到我的每个函数中。

    其次,我如何从 EitherT/ReaderT 堆栈中实际调用我的新类型类的函数?我不能简单地写
    foo :: ReadFile m => FilePath -> Ingest m Text
    foo = readFile
    

    因为这忽略了 EitherT 和 ReaderT 包装器。我要写这个吗?
    foo :: ReadFile m => FilePath -> Ingest m Text
    foo = lift . lift $ readFile
    

    对于变压器堆栈的结构来说,这似乎有点痛苦并且非常脆弱。我是否写了许多实例,例如
    instance ReadFile m => ReadFile (EitherT e m) where
      readFile = lift readFile
    

    ?这似乎也是令人沮丧的样板数量。

    最佳答案

    而不是使用 IO直接,将其包装在摘要中 newtype例如:

    module IOLog(IOLog, logMsg) where 
    
    newtype IOLog a = IOLog (IO a)
    
    instance Functor IOLog where ...
    
    instance Monad IOLog where ...
    
    logMsg :: String -> IOLog ()
    logMsg =  logIO . log
    
     -- local definitions --
     --
    logIO :: IO a -> IOLog a
    logIO =  IOLog
    
    log :: String -> IO ()
             .
             .
             .
    
    并用它来定义Ingest :
    type Ingest a = EitherT String (ReaderT Options IOLog) a
    
    通过这种方式,您可以控制程序其余部分可以使用的 I/O 子集,而只需支付额外模块的价格 - no type-system extensions needed!

    关于haskell - 如何创建有限版本的 IO monad,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48159763/

    相关文章:

    haskell - `readIORef`怎么会是阻塞操作

    haskell - 文件夹如何工作?

    haskell - Monad 变压器和提升功能

    haskell - 限制效果,例如 `Freer` ,使用 MTL 样式

    haskell - 处理一个 monad 调用另一个 monad 的干净方法是什么?

    scala - 如何向上转换 monad 变压器类型?

    arrays - Haskell 数组(矩阵)元素访问

    haskell - 如何将命令行参数传递给 stack exec

    haskell - 如何调试抖动规则执行?

    haskell - 自动将 Either 提升到 exceptT