-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpaf description seems a bit odd #90
Comments
For previous discussion, see #20 More feedback/input from the community (including yours) is always welcome. |
Hmm... No concrete examples...
Hmm... Let me write down a few examples then. I strongly suspect Rust mindset is sufficient.
Hmm... I don't think that's bad though, only shows that if you want to parse something strange - you can do it. As for applications being non obvious - let me write down some examples... use bpaf::*;
use std::path::PathBuf;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)] // tag for top level parser, used in every derive example
pub struct Options {
/// Increase verbosity
verbose: bool,
#[bpaf(external)] // least logical part, explained in tutorial used in most examples
dependency: Dependency,
}
#[derive(Debug, Clone, Bpaf)]
pub enum Dependency {
Local {
/// Add a local dependency
path: PathBuf,
},
Git {
/// Add a remote dependency
repository: String,
#[bpaf(external)]
rev: Rev,
},
}
#[derive(Debug, Clone, Bpaf)]
// out of 19 currently present API names fallback seems most logical
// https://docs.rs/bpaf/latest/bpaf/trait.Parser.html#method.fallback
#[bpaf(fallback(Rev::DefaultRev))]
pub enum Rev {
Branch {
/// Use this branch
branch: String,
},
Tag {
/// Use this tag
tag: String,
},
Revision {
/// Use this commit hash
rev: String,
},
#[bpaf(skip)]
DefaultRev,
}
fn main() {
println!("{:?}", options().fallback_to_usage().run());
} This gives you as a programmer a good structure that is easy to consume and a good help to user:
Most confusing part is here probably use of Let's make things more interesting. Instead of taking a single dependency to add we take several. Quick search on #[derive(Debug, Clone, Bpaf)]
#[bpaf(options)] // tag for top level parser, used in every derive example
pub struct Options {
/// Increase verbosity
verbose: bool,
- #[bpaf(external)]
- dependency: Dependency,
+ #[bpaf(external, many)]
+ dependency: Vec<Dependency>,
} Help for users automatically updates to indicate it takes multiple dependencies, programmer gets a vector in a field instead of a single struct. All the new logic is Say we want the set to be deduplicated and stored as - #[bpaf(external, many)]
- dependency: Vec<Dependency>,
+ #[bpaf(external, many, map(|x| x.into_iter().collect()))]
+ dependency: BTreeSet<Dependency>, Rust equivalent - just adding In currently unreleased version you can write - #[bpaf(external, many)]
- dependency: Vec<Dependency>,
+ #[bpaf(external, collect)]
+ dependency: BTreeSet<Dependency>, Say I want boxed string slices instead of strings. Same Branch {
/// Use this branch
#[bpaf(argument::<String>("BRANCH"), map(Box::from))]
branch: Box<str>,
}, |
Now let's go into something more obscure, say we want to implement a blazingly fast Command line looks like this: Best candidate to parse arbitrary item from a command line is Combining all those ideas we get a function
|
How do I parse this struct Options {
io: LinkedList<Io>,
}
enum Io {
Inputs(CustomHashSet<Box<str>>),
Outputs(CustomHashSet<Box<str>>),
} Which answer would you prefer?
|
@pacak I in the relevant comment, I quoted from you
With bpaf, you are very quick to jump into haskell-like ways of discussing things.
I don't have examples off the top of my head anymore but when I was coming to you saying "this can't be done", I don't think I was too far off the beaten path. |
Well, just for fun I decided to search for things like This specific quote comes from pacak/bpaf#50 while discussing why
I can't find specific conversation but I think we've been talking about validation - some argument is required iff some other arguments are present and user gets back a product type (struct) with fields wrapped in In my opinion this is not something users should do. One of the reasons I'm using Rust (and Haskell before) is that I can express a lot of invariants about data I'm working with in the types. If some process can be modeled as being in one of several states with some variables - I'll use What I was trying to explain is how to encode that invariant in the type system - nested enums where that conditional field goes into one branch with more variants and the rest go into separate branches. In my $dayjob we share bits of CLI parsers across multiple apps and this kind of representation is important so Rust compiler can tell you what needs to be updated. It is still possible to have a huge product type with optional fields and validation with Happy path I'm trying to get users into consists of
|
To set the tone: I think there are fascinating ideas in bpaf. I'm hopeful that the usability can be improved, whether directly in bpaf or if another comes along in the same vein. I'm also wanting to be careful to that we don't go too far off in this discussion, taking away from the main point of this discussion. I believe the overall focus is on how appropriate it is to "bless" bpaf and I'll be focusing on that.
I don't think the fact that a command-line argument parser has a category theory intro should be glossed over. My perception is also colored by the magazine article on the subject. As someone outside to the project, this helps further cement "this is haskell-complexity".
Naming is the most important documentation for a project. For examples of where bad names can come into play, I highlight some in a post of mine
That doesn't necessarily mean it isn't confusing. Evaluating by usage means there are other crates that would be considered first. I have also seen comments from others who have tried it and given up on it. Granted, one case I'm thinking of they also gave up on clap. I fully recognize that for trivial cases, for cases where someone is using something like |
This was not a tutorial on
Where is this complexity? Sure, if you want to parse If you set aside the category theory article and going off the documentation and examples - what makes it confusing? Ideally "I tried to do XXX expecting YYY, but got ZZZ instead". Or "I tried reading YYY and found the explanations unclear". I can't really fix "rough edges" and "confusing API" without knowing what those are. Is it just renaming |
I agree with the description of I had to click around for quite some time before finally finding a real example. I didn't go there originally because it's call "unusual" and I didn't want an unusual example. I just wanted a standard example. The API surface area is huge and there are a lot of moving pieces. The API is further obscured, IMO, by the use of generics. The top-level crate docs start with a single sentence about what the crate does and then immediately launches into "Design considerations for types produced by the parser," and I have no idea what the heck that means. I go to expand "A few examples," and all I see are incomplete code snippets that don't use the library at all.
I'd like to suggest you take this comment as an experience report about what I find complex rather than some objective claim over what is and isn't complex. |
Well... I will address at least some of the problems you listed. page on crates.io links to examples on github directly, I should keep something similar on the docs.rs as well. Will think about structuring documentation in a clearer way. As for API size - there's a |
I don't measure API size by counting API items. My measurement is subjective. Listen, you asked for specifics. So I gave you ten minutes of my time to go through your crate docs and API and rendered an opinion about it. Take it or leave it. I'm not getting into a tit-for-tat with you about clap. I gave you feedback about your crate based on questions you asked. I didn't do a comparative analysis between your crate and clap. |
I appreciate your feedback and will try to address it. |
FWIW I updated the documentation and rewrote the tutorials. A simple parser that parses a single flag fn main() {
let r = short('v').switch().run();
println!("{:?}", r);
} |
@pacak how about we table this until RustConf and power through some usability and documentation discussions then? I feel like there are some disconnects happening when talking about the docs and APIs that some in person, sync conversations might help with. |
Sure. I'll see what I can do about the navigation meanwhile. |
In particular what are those rough edges and which parts of the API are confusing?
Comments I encounter on the Internet are mostly in favor of it as having small and flexible API and with derive macro until you start doing strange things the difference with clap is mostly with what you derive and how you run it. And once you do start doing strange things - everything comes from a combination of a few rules similarly to how you would chain iterators...
https://news.ycombinator.com/item?id=35734051
The text was updated successfully, but these errors were encountered: