4

如何使用 Language.Haskell.Interpreter 读取给定的配置文件并分配其中给定的值以初始化程序中的变量?

我的配置文件是这样的:

numRecords = 10
numFields = 3
inputFile = /home/user1/project/indata.file
outputFile = /home/user1/project/outdata.file
datefmt = ddmmyyyy

我想用配置文件中给出的值初始化与配置文件中给出的标识符相对应的变量。

我如何使用 Language.Haskell.Interpreter 来完成这件事?我对 IO Monad 和 Interpreter Monad 感到困惑。这种类型的一个小例子也会很有用。

4

2 回答 2

7

为什么不?

data Config = Config {size :: Int, path :: String} deriving (Read, Show)

readConfig :: String -> Config
readConfig = read

main = do

  config <- readFile "my.config" >>= return . readConfig

  putStrLn $ "Configured size := " ++ show (size config)
  putStrLn $ "Configured path := " ++ show (path config)

使用my.config文件

Config {
  size = 1024,
  path = "/root/passwords.db"
}

并使用测试ghci

*Main> main
Configured size := 1024
Configured path := "/root/passwords.db"
*Main>

(对不起之前的错误,我很着急)

于 2013-05-23T14:25:39.067 回答
4

首先,您不能(至少以任何明显的方式)使用Language.Haskell.Interpreter提示来执行此操作。该模块中的函数用于读取和运行 Haskell 代码,而不是任意结构化数据。

要读取结构化数据,您将需要某种解析器。以下是您拥有的一些选项:

  1. 使用诸如Happy之类的解析器生成器。
  2. 使用解析器组合库,例如uu-parsinglibparsec
  3. 直接实现自己的解析器。
  4. 利用Read类的自动派生实例。

广告 1. 和 2。

如果您需要读取的数据格式非常重要,或者如果您需要有用的错误消息以防解析失败,我建议您选择 1. 或 2. 并查阅相应工具和库的文档。请注意,您将需要一些时间来习惯它们的主要概念和界面。

广告 3。

如果您的数据格式足够简单(如您的示例中所示),并且如果您的愿望清单中没有大量错误报告,您可以轻松推出自己的解析器。

在您的示例中,配置文件本质上是键和值的列表,由换行符分隔。在 Haskell 中,我们可以通过字符串对的列表来表示这样的列表:

type Config = [(String, String)]

“解析”配置然后简化为:(1)将输入字符串拆分为行,(2)将每一行拆分为单词,(3)从每行中选择第一个和第三个单词:

readConfig :: String -> Config
readConfig s =
  [(key, val) | line <- lines s, let (key : _ : val : _) = words line]

要从已解析的配置文件中检索条目,我们可以使用一个函数get

get :: String -> (String -> a) -> Config -> a
get key f config = case lookup key config of
  Nothing -> error ("get: not found: " ++ key)
  Just x  -> f x

该函数将条目的键作为其第一个参数,并将一个将原始值字符串转换为适当类型的函数作为其第二个参数。对于纯文本配置值,我们可以简单地将标识函数传递给get

inputFile, outputFile, datefmt :: Config -> String
inputFile  = get "inputFile" id
outputFile = get "outputFile" id
datefmt    = get "datefmt" id

对于整数条目,我们可以使用read

numRecords, numFields :: Config -> Int
numRecords = get "numRecords" read
numFields  = get "numFields" read

也许这些模式很常见,可以分解为它们自己的专用版本get

getS :: String -> Config -> String
getS key = get key id

getR :: Read a => String -> Config -> a
getR key = get key read

inputFile', outputFile', datefmt' :: Config -> String
inputFile'  = getS "inputFile"
outputFile' = getS "outputFile"
datefmt'    = getS "datefmt"

numRecords', numFields' :: Config -> Int
numRecords' = getR "numRecords"
numFields'  = getR "numFields"

例如,下面是读取配置文件并打印“outputFile”值的程序:

main :: IO ()
main = do
  s <- readFile "config.txt"
  let config = readConfig s
  putStrLn (outputFile config)

广告 4。

如果你可以控制配置文件的格式,你可以引入一个新的数据类型来保存配置数据,并让 Haskell 自动Read为它派生一个类的实例。例如:

data Config = Config
  { numRecords :: Int
  , numFields  :: Int
  , inputFile  :: String
  , outputFile :: String
  , datefmt    :: String
  } deriving Read

现在您需要确保您的配置文件符合预期的格式。例如:

Config
  { numRecords = 10
  , numFields  = 3
  , inputFile  = "/home/user1/project/indata.file"
  , outputFile = "/home/user1/project/outdata.file"
  , datefmt    = "ddmmyyyy"
  }

例如,下面是打印“outputFile”值的程序:

main :: IO ()
main = do
  s <- readFile "config.txt"
  let config = read s
  putStrLn (outputFile config)
于 2013-05-24T07:06:33.663 回答