alexpeits / harg

Haskell program configuration using higher kinded data
https://hackage.haskell.org/package/harg
BSD 3-Clause "New" or "Revised" License
29 stars 4 forks source link

Some ideas & questions #16

Open YPares opened 4 years ago

YPares commented 4 years ago

Hi! Nice ideas you've got there :) You've put together something that's pretty close to what I had in mind myself (I'm doing similar stuff in https://github.com/tweag/porcupine , but with vinyl).

I have some questions:

YPares commented 4 years ago
alexpeits commented 4 years ago

Hey @YPares, sorry for the late response - I must have missed a notification and literally just saw the issue! Thanks for the questions and suggestions, I will try to answer them in the same order:

Interestingly all of your points are on things I want to add very soon so I am very interested on all ideas/contributions!

YPares commented 4 years ago

So re. CLI, IMHO the simplest would be not to drive things via optparse-applicative. You could have your own datatype that'd contain field names and default value, and just use the optparse Opt as a backend. That's the way I did it in porcupine.

alexpeits commented 4 years ago

This how it was initially working (if I understand correctly what you mean). The issue was this: if I want to get the CLI value and then the env var and then the default (whatever is available first), I need to make it so that the parser always succeeds, which is easy because the f parameter in my HKD is Maybe so the value would be Nothing if it's not there. This means that optparse never fails for missing options (it only fails when it can't parse an option). But then if my environment variable parsing fails, I'm not in optparse-applicative-land anymore, so I need to render the errors on my own which is a bit inconsistent. An equally good option would be do all of the error reporting myself and not care about being consistent with optparse-applicative - which I am currently considering because having the CLI as a separate source would be sweet.

Do you have a specific example for how you do this?

YPares commented 4 years ago

I do it the other way: I start with the lowest priority environment, and parse only the CLI last. And I merge everything after that, with a flag in an HKD functor layer that indicates the source of the value (if it was from CLI, env vars), and I have an Ord instance over these sources flags to know which one has the priority. So my merging is actually commutative. So priority flags, default options and docstrings are all stored in my vinyl record via functor layers (it's via a HKD trick too, so it's not important it's an extensible record). Additionally, I have a Maybe layer so any field may be missing, but actually missing values seldom occur since I basically always have default values (so in the end, once everything is parsed, I just fail when a field is Nothing).

alexpeits commented 4 years ago

I see. This sounds similar to what I had in the first version and probably what I will be moving to in the future since being so tied to optparse-applicative isn't ideal. Although this might mean dropping the very limited support the library has for subcommands, but I don't see this as a major problem.

The only pain point is configuring the sources. For example, if you want a JSON file as a source, you first have to get the filename from the user somehow, then read it, decode it to your HKD and then get all the rest of the sources (and using optparse-applicative means that you can't just run one parser and then another, because one of them would find more options than it expects). It's done this way currently in harg but the implementation is not very easy to work with/extend..

So to summarize:

I'll post a gist with 2 and 4 as soon as I find some free time

YPares commented 4 years ago

http://hackage.haskell.org/package/optparse-generic maps the different constructors of a sum type to subcommands, maybe that could be doable here?

YPares commented 4 years ago

Re. getting the filenames, what I do is run a first simple, non optparse-applicative parsing of the raw list of args (I want the filenames to be the first elem of that list, and if it ends with .json or .yaml I consider it a filename and pop it, else I go straight to optparse phase). But that's a bit ugly.

alexpeits commented 4 years ago

The nice thing about making the first run work in a similar way as the rest is that you can just as well provide the path to the files that form the sources from environment variables. It does require a bit of plumbing though (I am putting a Const String before all the options the user supplies and run the parser with some defaults so it always succeeds).

solomon-b commented 2 years ago

Hi @alexpeits I'm looking into using HKDs for configuration parsing in a project. I would need commands in the arg parser and to make the arg parser able to be disabled per field.

harg unfortunately doesn't match these requirements, but based on your comments here it sounds like there isn't an obvious way to use optparse-applicative with HKDs regardless of harg and hit these 2 requirements?

If one were to greenfield a design with no attachment to existing libraries (such as optparse-applicative), do you see a way to solve these requirements and the other trade-offs mentioned in this issue or is this a deeper issue with using HKDs?

Also, are you still maintaining harg?