Haskell, how to use Language.Haskell.Interpreter to read a configuration file?

How to use Language.Haskell.Interpreter to read a given configuration file and assign the values ​​specified in it to initialize variables in my program?

My configuration file looks 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 specified in the configuration file, with the values ​​specified in the configuration file.

How can I use Language.Haskell.Interpreter to complete this task? I am confused because of the Monad MO and the Monad translator. A small example of this kind will also be useful.

+4
source share
2 answers

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

To read in structured data, you need some kind of parser. Here are some of the options you have:

  • Use a parser generator like Happy .
  • Use a parser-combinator library such as uu-parsinglib or parsec .
  • Directly implement your own parser.
  • Use automatically derived instances of the Read class.

Announcement 1. and 2.

If the data format you need to read is nontrivial, or if you need useful error messages if parsing is not performed, I recommend switching to 1. or 2. and consult the documentation of the corresponding tools and library. Keep in mind that it will take you some time to get used to their basic concepts and interfaces.

Announcement 3.

If the format of your data is simple enough (as in your example), and if extensive reporting of errors is small on your wish list, you can easily collapse your own parser.

In your example, the configuration file is a list of keys and values ​​separated by newlines. In Haskell, we can imagine this enumeration from a list of line pairs:

 type Config = [(String, String)] 

The “analysis” of the configuration then reduces to: (1) splitting the input line in the lines, (2) splitting each line with words, (3) selecting the first and third words from each line:

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

To get an entry from the analyzed configuration file, we can then use the get function:

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

This function takes the record key as its first argument, and the second argument takes the function that converts the string of the original value to something suitable. For purely textual configuration values, we can simply pass the get identification function:

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

For whole 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 considered in their own 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 a program that reads in a configuration file and prints the value for "outputFile":

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

Announcement 4.

If you can control the format of the configuration file, you can enter a new data type to store the configuration data and Haskell will automatically display an instance of the Read class for it. For instance:

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

Now you need to make sure your configuration files are in 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 a program that prints the value for "outputFile":

 main :: IO () main = do s <- readFile "config.txt" let config = read s putStrLn (outputFile config) 
+4
source

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 mistakes, I was in a hurry)

+6
source

Source: https://habr.com/ru/post/1482389/


All Articles