有没有什么比unsafePerformIO更适合这个....?

到目前为止,我避免需要unsafePerformIO ,但今天可能不得不改变......我想看看社区是否同意,或者如果有人有更好的解决方案。

我有一个库需要使用存储在一堆文件中的一些配置数据。 这些数据保证是静态的(在运行过程中),但需要在可以(在极少数情况下)由不能编译Haskell程序的最终用户编辑的文件中。 (细节是不重要的,但将“/etc/mime.types”视为一个相当不错的近似值,它是一个在很多程序中使用的大型静态数据文件)。

如果这不是一个库,我只会使用IO monad ....但是因为它是一个在我的代码中调用的库,它实际上迫使IO monad冒泡通过几乎所有我用多个模块! 尽管我需要对数据文件进行一次读取,但这种低级调用是纯粹的,所以这是一个非常不可接受的结果。

仅供参考,我打算将该调用包装在unsafeInterleaveIO中,以便仅加载所需的文件。 我的代码看起来像这样....

dataDir="<path to files>"

datafiles::[FilePath]
datafiles = 
  unsafePerformIO $
  unsafeInterleaveIO $
  map (dataDir </>) 
  <$> filter (not . ("." `isPrefixOf`))
  <$> getDirectoryContents dataDir

fileData::[String]
fileData = unsafePerformIO $ unsafeInterleaveIO $ sequence $ readFile <$> datafiles

鉴于读取的数据是透明的,我非常肯定unsafePerformIO是安全的(这已经在很多地方讨论过了,比如“Use of unsafePerformIO appropriate?”)。 不过,如果有更好的方法,我很乐意听到它。


最新情况:

为了回应Anupam的评论....

有两个原因让我无法将lib分解为IO和非IO部分。

首先,数据量很大,我不想一次将它全部读入内存。 请记住,IO总是严格阅读....这就是为什么我需要放入unsafeInterleaveIO调用,以使其懒惰。 恕我直言,一旦你使用unsafeInterleaveIO ,你也可以使用unsafePerformIO ,因为风险已经存在。

其次,突破IO特定部分只是用IO读取代码冒泡以及数据传递(我实际上可能选择使用状态monad传递数据来替代IO monad的冒泡无论如何,因此,将IO monad替换为状态monad并不是一种改进)。 如果低级函数本身不是有效的纯粹的话,这并不会那么糟糕(例如,考虑上面的我的/etc/mime.types示例,并想象一个Haskell extensionToMimeType函数,它基本上是纯粹的,但需要获取数据库中的数据......突然间,堆栈中所有从低到高的内容都需要调用或通过一个readMimeData::IO String 。为什么每个main甚至需要关心一个子模块深层的库选择? )。


我同意Anupam Jain的观点,你最好是在IO级中读取这些数据文件的更高级别,然后纯粹通过其他程序将数据传递给它们。

例如,您可以将需要fileData结果的fileData放入Reader [String] ,以便他们可以根据需要(或某些Reader Config ,其中Config包含这些字符串以及您需要的其他任何内容)询问结果。

我建议的草图如下:

type AppResult = String

fileData :: IO [String]
fileData = undefined -- read the files

myApp :: String -> Reader [String] AppResult
myApp s = do
  files <- ask
  return undefined -- do whatever with s and config

main = do
  config <- fileData
  return $ runReader (myApp "test") config

我收集到你不想一次读完所有的数据,因为那样会很昂贵。 也许你并不真正知道你需要加载哪些文件,因此在开始时加载所有这些文件会很浪费。

这是一个解决方案的尝试。 它要求你在一个免费的monad里面工作,并将这些副作用的操作交给解释者。 一些初步进口:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import qualified Data.ByteString as B
import Data.Monoid
import Data.List
import Data.Functor.Compose
import Control.Applicative
import Control.Monad
import Control.Monad.Free
import System.IO

我们为免费的monad定义了一个functor。 它将提供一个值, p做解释,并继续接收值之后,计算b

type LazyLoad p b = Compose ((,) p) ((->) b)

一种方便的功能来请求加载文件:

lazyLoad :: FilePath -> Free (LazyLoad FilePath B.ByteString) B.ByteString  
lazyLoad path = liftF $ Compose (path,id) 

stdin读取“文件内容”的虚拟解释器函数:

interpret :: Free (LazyLoad FilePath B.ByteString) a -> IO a 
interpret = iterM $ (Compose (path,next)) -> do
    putStrLn $ "Enter the contents for file " <> path <> ":"
    B.hGetLine stdin >>= next

一些愚蠢的例子功能:

someComp :: B.ByteString -> B.ByteString
someComp b = "[" <> b <> "]"

takesAwhile :: Int
takesAwhile = foldl' (+) 0 $ take 400000000 $ intersperse (negate 1) $ repeat 1

一个示例程序:

main :: IO ()
main = do
    r <- interpret $ do 
        r1 <- someComp <$> lazyLoad "file1"
        r2 <- return takesAwhile 
        if (r2 == 1)
            then return r1
            else someComp <$> lazyLoad "file2"
    putStrLn . show $ r

执行时,该程序将请求一条线,花费一些时间计算takesAwhile ,然后才请求另一条线。

如果想要允许不同种类的“请求”,这个解决方案可以扩展为类似单点数据类型的东西,这样每个功能只需要知道它需要的精确效果。

如果满足于仅允许一种类型的请求,则还可以使用Pipes.Core中的ClientServer而不是免费的monad。

链接地址: http://www.djcxy.com/p/7489.html

上一篇: Is there something better than unsafePerformIO for this....?

下一篇: is GHC's implementation of Haskell semantically broken?