[Haskell-cafe] Config Files and monoids

lennart spitzner hexagoxel at hexagoxel.de
Thu May 24 22:45:20 UTC 2018


Thanks matt for this pointer. I have also used the higher-kinded approach in the past, but using a slightly different abstraction. This is introduced in a very recent post [1]. I think this gives you the custom `gtraverse` you describe essentially for free (I assume you'd still need to write that, given that it has a custom type, right?). However it does not use the type family trick to avoid the `Identity` wrappers.

The examples in my post still mostly assume that there is a default config, but I think you could work around this. This would involve the `CZipWithM` class instead of just `CZipWith`: The simple path would be

  cTraverse (fmap Identity) :: MyConfig Option -> Option (MyConfig Identity)

which roughly corresponds to `gvalidate` from the "Higher-Kinded Data" post. The downside is that a Nothing result would not tell you which field(s) were missing. To fix that, you could define a static value of type `MyConfig (Const String)` that adds a value-level name to each field, and use `cZipWithM` to produce a `Either String (MyConfig Identity)` or perhaps even `Either [String] (MyConfig Identity)` by using the right traversal monad.

Hope this helps.

-- lennart

[1] http://hexagoxel.de/postsforpublish/posts/2018-05-24-program-configuration.html


On 25/05/18 00:03, Matt wrote:
> 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 at 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.
> 
> 
> 
> _______________________________________________
> 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.
> 



More information about the Haskell-Cafe mailing list