从文件加载纯全局变量
我有一个包含一些数据的文件。 这些数据永远不会改变,我想让它在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
列表将被实际读取和创建懒惰!
上一篇: Load pure global variable from file
下一篇: Difference between type constructor and return function of a monad (in Haskell)