Wrapping all fields of a data type in e.g. Maybe

I have a common pattern in my command-line programs; I start out with a configuration data type, which over-simplified looks like: data Cfg = Cfg { verbose :: Bool } Now, there's usually a default configuration, default :: Cfg default = Cfg False The user can override the defaults one of two ways, either via a config file, or from the command-line. If both are specified, the command-line takes precedence. The way I do this is with, data OptionalCfg = OptionalCfg { verbose :: Maybe Bool } And then I define I Monoid instance for OptionalCfg which lets me merge two ofthem. Once the two OptionalCfgs are merged, I merge *that* with the default Cfg. This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?

On Tue, Jul 16, 2013 at 04:57:59PM -0400, Michael Orlitzky wrote:
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
You can always try data Cfg f = Cfg { verbose :: f Bool } and set f to Maybe or Identity depending on what you use it for. It will be slightly notationally cumbersome to extract values from the Identity functor though. Tom

On 07/16/2013 05:06 PM, Tom Ellis wrote:
On Tue, Jul 16, 2013 at 04:57:59PM -0400, Michael Orlitzky wrote:
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
You can always try
data Cfg f = Cfg { verbose :: f Bool }
and set f to Maybe or Identity depending on what you use it for. It will be slightly notationally cumbersome to extract values from the Identity functor though.
Two votes for this approach. I'll give it a try and see whether it comes out more or less verbose. Thanks!

On 07/16/2013 09:57 PM, Michael Orlitzky wrote:
I have a common pattern in my command-line programs; I start out with a configuration data type, which over-simplified looks like:
data Cfg = Cfg { verbose :: Bool }
Now, there's usually a default configuration,
default :: Cfg default = Cfg False
The user can override the defaults one of two ways, either via a config file, or from the command-line. If both are specified, the command-line takes precedence. The way I do this is with,
data OptionalCfg = OptionalCfg { verbose :: Maybe Bool }
And then I define I Monoid instance for OptionalCfg which lets me merge two ofthem. Once the two OptionalCfgs are merged, I merge *that* with the default Cfg.
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
One option is to combine OptionalCfg with Cfg, by parameterizing Cfg. If you make data Cfg a = Cfg { cfgVerbose :: a Bool } Then you can choose between Cfg Identity and Cfg Maybe. Furthermore, these are different types, so you can still have a monoid over Cfg Maybe. There might be some lens magic that makes working with this easier too. For example, verbose :: Lens' (Cfg a) Bool verbose = cfgVerbose.traverse Or something to that effect (that actually compiles). - ollie

The suggestion of parameterizing on a functor would be good, however
there's another approach I've often seen (although it's not quite what
you've asked for). You can leave your config datatype alone, but instead
of making it a monoid have your configuration parsers return functions with
the type (Cfg -> Cfg). You can wrap these functions in Endo to get a
monoid, combine them together, and then apply that function to the default
configuration.
On Wed, Jul 17, 2013 at 4:57 AM, Michael Orlitzky
I have a common pattern in my command-line programs; I start out with a configuration data type, which over-simplified looks like:
data Cfg = Cfg { verbose :: Bool }
Now, there's usually a default configuration,
default :: Cfg default = Cfg False
The user can override the defaults one of two ways, either via a config file, or from the command-line. If both are specified, the command-line takes precedence. The way I do this is with,
data OptionalCfg = OptionalCfg { verbose :: Maybe Bool }
And then I define I Monoid instance for OptionalCfg which lets me merge two ofthem. Once the two OptionalCfgs are merged, I merge *that* with the default Cfg.
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Oh, very nice. It seems reasonable to extend this to Cfg -> IO Cfg to
support things like dynamically loading config files, if needed.
On Jul 16, 2013 5:42 PM, "John Lato"
The suggestion of parameterizing on a functor would be good, however there's another approach I've often seen (although it's not quite what you've asked for). You can leave your config datatype alone, but instead of making it a monoid have your configuration parsers return functions with the type (Cfg -> Cfg). You can wrap these functions in Endo to get a monoid, combine them together, and then apply that function to the default configuration.
On Wed, Jul 17, 2013 at 4:57 AM, Michael Orlitzky
wrote: I have a common pattern in my command-line programs; I start out with a configuration data type, which over-simplified looks like:
data Cfg = Cfg { verbose :: Bool }
Now, there's usually a default configuration,
default :: Cfg default = Cfg False
The user can override the defaults one of two ways, either via a config file, or from the command-line. If both are specified, the command-line takes precedence. The way I do this is with,
data OptionalCfg = OptionalCfg { verbose :: Maybe Bool }
And then I define I Monoid instance for OptionalCfg which lets me merge two ofthem. Once the two OptionalCfgs are merged, I merge *that* with the default Cfg.
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On 07/16/2013 08:41 PM, John Lato wrote:
The suggestion of parameterizing on a functor would be good, however there's another approach I've often seen (although it's not quite what you've asked for). You can leave your config datatype alone, but instead of making it a monoid have your configuration parsers return functions with the type (Cfg -> Cfg). You can wrap these functions in Endo to get a monoid, combine them together, and then apply that function to the default configuration.
I'm using cmdargs for the command-line parsing, and I think (if I don't want to abandon its magic entirely) that I'm stuck filling a data structure automatically. I settled on using (Maybe Foo) so that the default value returned by cmdargs will be Nothing if the user doesn't supply that option; if I use a plain Cfg object, and the user doesn't pass --verbose, I'll get False back in its place and then I don't know whether or not that should override the config file (supposing the user has verbose=True in the file). Very clever though.

On 07/16/2013 04:57 PM, Michael Orlitzky wrote:
This all works great, except that when there's 20 or so options, I duplicate a ton of code in the definition of OptionalCfg. Is there some pre-existing solution that will let me take a Cfg and create a new type with Cfg's fields wrapped in Maybe?
For posterity, I report failure =) If I parameterize the Configuration type by a functor, it breaks the DeriveDataTypeable magic in cmdargs. The resulting manual definitions along with the lenses to look inside the Identity functor well exceed the duplicated code from OptionalCfg. Combining the option parsing and config file parsing increases the amount of code in the command-line parser by roughly an equal amount, but in my opinion a worse consequence is that it conflates two unrelated procedures. I very much like this: rc_cfg <- from_rc cmd_cfg <- apply_args let opt_config = rc_cfg <> cmd_cfg ... All things considered the duplicated data structure seems like the least of three evils.

On Sat, Jul 20, 2013 at 12:14 AM, Michael Orlitzky
For posterity, I report failure =)
Hi Michael, It's fairly straightforward to generate the new data with template haskell [1], and on the same page, section "10.7 'generic' zipWith" is likely to be similar to your merging code. [1] http://www.haskell.org/haskellwiki/Template_Haskell#Generating_records_which... -- Adam

On 07/20/2013 04:49 PM, adam vogt wrote:
Hi Michael,
It's fairly straightforward to generate the new data with template haskell [1], and on the same page, section "10.7 'generic' zipWith" is likely to be similar to your merging code.
[1] http://www.haskell.org/haskellwiki/Template_Haskell#Generating_records_which...
I don't know any TH yet, but this looks like it just might work. Thanks for the suggestion!
participants (6)
-
adam vogt
-
David Thomas
-
John Lato
-
Michael Orlitzky
-
Oliver Charles
-
Tom Ellis