到目前为止,我已经避免了需要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甚至需要关心多层深度子模块的库选择?).
我同意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)