对于这个,有什么比unsafePerformIO更好的东西吗?

jam*_*idh 5 haskell

到目前为止,我已经避免了需要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
Run Code Online (Sandbox Code Playgroud)

鉴于读取的数据是引用透明的,我很确定unsafePerformIO是安全的(这已在很多地方讨论过,例如" 使用unsafePerformIO合适吗? ").不过,如果有更好的方法,我很乐意听到它.


最新情况:

回应Anupam的评论......

有两个原因导致我无法将lib分解为IO和非IO部分.

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

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

ama*_*loy 5

我同意Anupam Jain的意见,你最好在IO中稍高一些级别读取这些数据文件,然后将其中的数据纯粹传递给你的其余程序.

例如,您可以将需要结果的函数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
Run Code Online (Sandbox Code Playgroud)