从文件加载纯全局变量



我有一个文件,里面有一些数据。这些数据永远不会改变,我想让它在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真的是一个全局常数,并且您不想在程序源中包含大量数据,那么可以使用dflemstr演示的TemplateHaskell。

是的,应该没问题。为了安全起见,您可以添加{-# NOINLINE primes #-}杂注——不确定GHC是否会内联CAF。

我能想到的唯一替代方案是在编译时做同样的事情(使用TemplateHaskell),本质上是将素数嵌入二进制中。然而,我更喜欢你的版本——注意,primes列表实际上将被阅读&懒散地创造!

加载该文件时,程序没有准确定义。如果该文件不存在,这将引发异常,并且不知道具体发生在哪里。(也就是说,可能是在你的程序已经做了一些可观察到的真实世界的事情之后。)如果有人决定更改文件的内容,类似的备注也适用;你不知道它什么时候被阅读,也不知道你会得到哪些内容。(如果文件不应该更改,则不太可能成为问题。)

至于替代方案:一种可能性是创建一个全局可变变量[这本身就有点邪恶],并从主I/O线程将文件的内容插入到该变量中。这样,文件就可以在定义明确的时刻被读入。[我注意到你也在使用惰性I/O,所以你只会在文件打开时进行定义。]

实际上,"正确"的做法是手动将数据线程到每个需要它的函数。我可以理解为什么你可能不想这样做;这是一种痛苦。您可能会使用某种状态monad来避免手动执行此操作。。。

这是基于dflemstr的答案。如果你想加载一个整数列表可能也希望在编译时执行CCD_ 22。我只是把它写出来,因为看到这个例子对我来说会很有用,我希望它能帮助其他人。

import Language.Haskell.TH
import Language.Haskell.TH.Quote
intArray' :: String -> Q Exp
intArray' s = return $ ListE e
where
e = map (LitE . IntegerL . read) $ words s
intArray :: QuasiQuoter
intArray = QuasiQuoter { quoteExp = intArray' }

intArrayFile :: QuasiQuoter
intArrayFile = quoteFile intArray

使用它…

{-# LANGUAGE QuasiQuotes #-}
import TT
primes :: [Int]
primes = [intArrayFile|primes.txt|]
main = print primes

好处是

  • 编译时语法检查primes.txt文件
  • 运行时没有转换以降低速度或引发异常
  • 潜在的代码大小改进,因为您不需要原始存储整个文件

相关内容

  • 没有找到相关文章

最新更新