Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell how to use Language.Haskell.Interpreter to read config file?

How do I use the Language.Haskell.Interpreter to read the given config file and assign the values given in it to initialize variables in my program?

My config file is like:

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

I want to initialize the variables corresponding to the identifiers given in the config file with the values given in the config file.

How do I use the Language.Haskell.Interpreter to accomplish this thing? I am confused because of the IO Monad and the Interpreter Monad. A small example of this kind will also be useful.

like image 833
Tem Pora Avatar asked Dec 20 '22 05:12

Tem Pora


2 Answers

Why not?

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)

Using my.config file

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

And testing using ghci

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

(sorry previous bugs, I was hurry)

like image 72
josejuan Avatar answered May 01 '23 10:05

josejuan


First of all, you cannot (in any obvious way, at least) use Language.Haskell.Interpreter from the hint package to do this. That functions in that module are used to read in and run Haskell code, not arbitrary structured data.

For reading in structured data, you will need a parser of some sort. Here are some of the options that you have:

  1. Use a parser generator such as Happy.
  2. Use a parser-combinator library such as uu-parsinglib or parsec.
  3. Directly implement your own parser.
  4. Make use of automatically derived instances of the Read class.

Ad 1. and 2.

If the format of the data you need to read in is nontrivial or if you need helpful error messages in case parsing fails, I'd recommend to go for 1. or 2. and to consult the documentation of the respective tools and libraries. Be aware that you will need some time to get accustomed to their main concepts and interfaces.

Ad 3.

If the format of your data is simple enough (as it is in your example) and if extensive error reporting is not high on your wish list, you can easily roll your own parser.

In your example, a config file is essentially a listing of keys and values, seperated by newlines. In Haskell, we can represent such a listing by a list of pairs of strings:

type Config = [(String, String)]

"Parsing" a configuration then reduces to: (1) splitting the input string in lines, (2) splitting each line in words, (3) selecting from each line the first and the third word:

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

To retrieve an entry from a parsed configuration file, we can then use a function 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

This function takes as its first argument the key of the entry and as its second argument a function that converts the raw value string into something of the appropriate type. For purely textual configuration values we can simply pass the identity function to get:

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

For integer entries we can use read:

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

Perhaps these patterns are common enough to be factored out into their own dedicated versions of 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"

As an example, here is program that reads in a configuration file and that prints the value for "outputFile":

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

Ad 4.

If you can control the format of the configuration file, you can introduce a new datatype for holding configuration data and have Haskell automatically derive an instance of the class Read for it. For instance:

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

Now you will need to make sure that your configuration files match the expected format. For instance:

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

As an example, here is then the program that prints the value for "outputFile":

main :: IO ()
main = do
  s <- readFile "config.txt"
  let config = read s
  putStrLn (outputFile config)
like image 34
Stefan Holdermans Avatar answered May 01 '23 11:05

Stefan Holdermans