从文件加载纯全局变量

我有一个包含一些数据的文件。 这些数据永远不会改变,我想让它在IO monad之外可用。 我怎样才能做到这一点?

示例(请注意,这只是一个示例,我的数据不可计算):

primes.txt:

2 3 5 7 13

code.hs:

primes :: [Int]
primes = map read . words . unsafePerformIO . readFile $ "primes.txt"

这是否是“合法”使用unsafePerformIO ? 有替代品吗?


您可以使用TemplateHaskell在编译时读取文件。 该文件的数据将作为实际字符串存储在程序中。

在一个模块(本例中为Text/Literal/TH.hs )中,定义如下:

module Text.Literal.TH where

import Language.Haskell.TH
import Language.Haskell.TH.Quote

literally :: String -> Q Exp
literally = return . LitE . StringL

lit :: QuasiQuoter
lit = QuasiQuoter { quoteExp = literally }

litFile :: QuasiQuoter
litFile = quoteFile lit

在你的模块中,你可以这样做:

{-# LANGUAGE QuasiQuotes #-}
module MyModule where

import Text.Literal.TH (litFile)

primes :: [Int]
primes = map read . words $ [litFile|primes.txt|]

在编译程序时,GHC将打开primes.txt文件并将其内容插入[litFile|primes.txt|]部件的位置。


以这种方式使用unsafePerformIO并不好。

声明primes :: [Int]表示primes是一个数字列表。 一个特定的数字列表,不依赖于任何内容。

但实际上,当定义恰好被评估时,它取决于文件“primes.txt”的状态。 有人可以改变这个文件来改变primes看起来有的值,根据它的类型这是不应该的。

如果存在一个假设的优化,它决定primes应该按需求重新计算而不是完全存储在内存中(毕竟,它的类型表示每次我们重新计算它时都会得到相同的结果), primes甚至可能似乎有在程序的单次运行中有两个不同的值。 这是使用unsafePerformIO进行编译的一个问题。

在实践中,以上所有可能都不是问题。

但是理论上正确的做法是不要使质primes成为全局常量(因为它不是常数)。 相反,你需要对它进行参数化的计算(即将质primes作为参数),然后在外部IO程序中读取文件,然后通过传递从文件中提取的IO程序的纯值来调用纯计算。 你得到两全其美的好处; 你不必对编译器撒谎,也不必将整个程序放入IO 。 您可以使用Reader monad等结构来避免在任何地方手动传递primes (如果有帮助的话)。

所以你可以使用unsafePerformIO如果你想继续使用它。 这在理论上是错误的,但不太可能在实践中引起问题。

或者你可以重构你的程序来反映真实情况。

或者,如果primes真的是全局常量,并且您不想在程序源代码中包含大量数据,则可以使用TemplateHaskell,如dflemstr所示。


是的,它应该没问题。 您可以添加{-# NOINLINE primes #-}编译器以确保安全 - 不确定GHC是否会嵌入CAF。

我能想到的唯一选择是在编译时(使用Template Haskell)做同样的事情,本质上是将素数嵌入到二进制文件中。 不过,我更喜欢你的版本 - 请注意, primes列表将被实际读取和创建懒惰!

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

上一篇: Load pure global variable from file

下一篇: Difference between type constructor and return function of a monad (in Haskell)