For a "fully general" approach, the problem is well expressed by the "higher kinded data" pattern: http://reasonablypolymorphic.com/blog/higher-kinded-data/

A `Config f = Config { configFoo :: f Foo, ... }` type would use either the First or Last monoids, depending on if you want earlier updates to take precedence over later ones. Then, you would get a `Config First` from your CLI parser, a `Config First` from your environment variable parser, and a `Config First` from your config file parser. After `mappend`ing them all together, you'd use a `gtraverse` function with a signature like: `Config First -> Either [Text] (Config Identity)` -- you'd either have a list of all fields that were missing, or a complete Config.

Matt Parsons

On Thu, May 24, 2018 at 3:00 PM, Olaf Klinke <olf@aatal-apotheke.de> wrote:
Dear cafe,

a recent post here [1] mentioned that configurations, such as the ones read from a config file, can be given Monoid instances, where mempty is the empty or default configuration and mappend merges two partial configurations, producing a more complete one. The vgrep package explicitly does this, for instance. Although the ConfigParser type from the ConfigFile package has a binary 'merge' operation, it does define neither a Monoid not a Semigroup instance.

I'm struggling to make the concept of monoidal configuration work when there is no sensible default configuration. Suppose my configuration type is

data Config = Config {foo :: Bool, bar :: Int}

with no reasonable default, e.g.

emptyConfig = Config {
  foo = error "you did not specify option foo",
  bar = error "you did not specify option bar"
  }

Some configuration monoids seem to have the second operand override the first, or the other way around. However, I wish that when
cfg1 = emptyConfig {foo = True}
cfg2 = emptyConfig {bar = 4}
then cfg1 <> cfg2 == Config {foo = True, bar = 4}.

So it seems that for mappend to work as intended one needs a terminating function that tells me if a record field is already defined, e.g. when all fields are Maybes. Vgrep.Environment.Config.Monoid does it this way. My solution so far was to resort to the monoid of endofunctions (as the getflag package does), that is, define

cfg1, cfg2 :: Config -> Config
cfg1 = \cfg -> cfg {foo = True}
cfg2 = \cfg -> cfg {bar = 4}

And then build (cfg1.cfg2) emptyConfig. (Alternatively, one might structure these as lenses instead of endofunctions, see e.g. Data.Monoid.Endo.Fold in the endo package.)
Thus I arrived at

class Config cfg where
  emptyConfig   :: cfg -- may contain some defaults
  configOptions :: [Parser (cfg -> cfg)]

Do you think every other concept of configuration parsing can be cast into this typeclass?
-- Olaf

[1] https://mail.haskell.org/pipermail/haskell-cafe/2018-May/129063.html
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.