Parsing arguments and reading configuration

Hi all, I'm looking for some inspiration for an elegant solution to a silly little problem I have. This might have a general well-known solution, or maybe there's something particularly elegant possible in Haskell. I just thought I'd ask. When writing a command line tool I want to use a configuration file and then have the ability to override the configuration using command line arguments. When I've worked with command line arguments before I've used the trick of folding (>>=) over a list of functions that modify the "members" of a type, using the default values as the starting point. I like that, it's cute. First I thought I'd treat the configuration in a similar way, but then I noticed a slight ordering problem. The command line arguments should take priority over the contents of the configuration file, but the location of the configuration can be given as an argument. I could read the arguments twice, first to get the correct location of the config file, then load the config, and then read the arguments again to make sure they take priority. But that feels a little silly. Are there any more elegant solutions people are using? /M -- Magnus Therning (OpenPGP: 0xAB4DFBA4) magnus@therning.org Jabber: magnus@therning.org http://therning.org/magnus Haskell is an even 'redder' pill than Lisp or Scheme. -- PaulPotts

On Fri, 2008-09-19 at 23:24 +0100, Magnus Therning wrote:
Hi all,
I'm looking for some inspiration for an elegant solution to a silly little problem I have. This might have a general well-known solution, or maybe there's something particularly elegant possible in Haskell. I just thought I'd ask.
When writing a command line tool I want to use a configuration file and then have the ability to override the configuration using command line arguments. When I've worked with command line arguments before I've used the trick of folding (>>=) over a list of functions that modify the "members" of a type, using the default values as the starting point. I like that, it's cute.
First I thought I'd treat the configuration in a similar way, but then I noticed a slight ordering problem. The command line arguments should take priority over the contents of the configuration file, but the location of the configuration can be given as an argument. I could read the arguments twice, first to get the correct location of the config file, then load the config, and then read the arguments again to make sure they take priority. But that feels a little silly. Are there any more elegant solutions people are using?
You could build a monoid, data Option a = Unspecified | Default a | Config a | CommandLine a With Unspecified being the identity and the multiplication being Default a * Config b = Config b Default a * CommandLine b = CommandLine b Config a * CommandLine b = CommandLine b and symmetrically and break ties to the right e.g. CommandLine a * CommandLine b = CommandLine b

2008/9/19 Magnus Therning
First I thought I'd treat the configuration in a similar way, but then I noticed a slight ordering problem. The command line arguments should take priority over the contents of the configuration file, but the location of the configuration can be given as an argument. I could read the arguments twice, first to get the correct location of the config file, then load the config, and then read the arguments again to make sure they take priority. But that feels a little silly. Are there any more elegant solutions people are using?
I'm not sure how well it would hold up under maintenance, but you coud have a config sum-type which is itself a monoid, and then create two of them:
data UserConfig = UserConfig { item1 :: Maybe Type1 , item2 :: Maybe Type2 , configFileLocation :: Maybe FilePath }
instance Monoid UserConfig where {- not shown -}
buildConfig :: IO UserConfig buildConfig = do cmdLineCfg <- buildConfigFromCmdLine fileCfg <- maybe (return mempty) buildConfigFromFile (configFileLocation cmdLineCfg)
return $ fileCfg `mappend` cmdLineCfg
-- mappend is assumed to be left-biased
buildConfigFromCmdLine :: IO UserConfig buildConfigFromFile :: FilePath -> IO UserConfig
Does that make sense? or is it too complicated? -Antoine

On Fri, Sep 19, 2008 at 7:35 PM, Antoine Latter
I'm not sure how well it would hold up under maintenance, but you coud have a config sum-type which is itself a monoid, and then create two of them:
And by sum-type I mean product type. Sheesh. Although having your config options in a sum-type packed into a Set, which is itself a Monoid is another option. Then you get 'mempty' and 'mappend' for free. I think I saw a blog-post or something detailing this, but I don't have a book-mark. -Antoine

Alternately, just go with a map initially with default values. Then parse the command line args into a second map (especially if they're all of a format like -argname argvalue). Then lookup your args file with the command line map, and failing that the default map. Then read the args file and finally merge all three maps in the proper order. No need for monoid instances, special data types or any of that, so at a little cost in elegance a much more succinct way to get what you want. As a final step, you can always project the map values into a record type, to get some safety for the rest of your program. The entire process, aside from creating the record, should probably be no more than four or so lines. --Sterl. On Sep 19, 2008, at 8:39 PM, Antoine Latter wrote:
On Fri, Sep 19, 2008 at 7:35 PM, Antoine Latter
wrote: I'm not sure how well it would hold up under maintenance, but you coud have a config sum-type which is itself a monoid, and then create two of them:
And by sum-type I mean product type. Sheesh.
Although having your config options in a sum-type packed into a Set, which is itself a Monoid is another option. Then you get 'mempty' and 'mappend' for free.
I think I saw a blog-post or something detailing this, but I don't have a book-mark.
-Antoine _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Sun, Sep 21, 2008 at 4:02 PM, Sterling Clover
Alternately, just go with a map initially with default values. Then parse the command line args into a second map (especially if they're all of a format like -argname argvalue). Then lookup your args file with the command line map, and failing that the default map. Then read the args file and finally merge all three maps in the proper order. No need for monoid instances, special data types or any of that, so at a little cost in elegance a much more succinct way to get what you want. As a final step, you can always project the map values into a record type, to get some safety for the rest of your program. The entire process, aside from creating the record, should probably be no more than four or so lines.
Yes, that's the direction I'll end up going in, I think. The idea with the monoid was nice, but I couldn't really turn it into a usable idea in my head, since the whole idea with an entity turns out to be somewhat strange. At least in the way I look at arguments. However, the idea of using a product to "merge" to configurations is interesting and I think I might explore it more when I find the time. (So far I've only identified that I probably will have use for template haskell once I decide to play more with it.) Thanks for the help and suggestions offered on the list. /M -- Magnus Therning (OpenPGP: 0xAB4DFBA4) magnus@therning.org Jabber: magnus@therning.org http://therning.org/magnus identi.ca|twitter: magthe
participants (4)
-
Antoine Latter
-
Derek Elkins
-
Magnus Therning
-
Sterling Clover