Hi all,
So I had a script that needed an option parser, and naturally, I turned to OptionParser. Looking at the `
OptionParser.parse/2` docs, I went and implemented my parser, using `
switches: [host: string, observer: boolean]`.
Having set up a basic `
parse_args` function that checked for invalid args, I went and ran my problem with `
elixir program.exs --foo`, picking an arbitrary argument and expecting it to fail. To my surprise, it succeeded, and I ended up with a `
foo: true` option.
Now, I imagine anyone who's quite familiar with OptionParser at this point is probably already saying, "sure, it dynamically parsed that; you want to use `
strict` and not `
switches`". But the response from a few regulars on the Elixir Slack was "huh what?", followed by a long back-and-forth testing session in which we were all getting different results and having trouble reproducing each other's findings, and (after figuring out what was going on) ultimately resulting in a deep dive into Erlang crash logs and finding that the `
foo` atom was defined in the `
sots` Erlang module, which was only loaded if you run `
elixir <file>` or `
mix run <file>` but not if you run `
elixir -e "code"`, and may or may not be defined in `
iex` depending on your command history, etc etc.
Adding to the confusion is this paragraph from the `parse/2` documentation:
OptionParser also includes a dynamic mode where it will attempt to parse switches dynamically. Such can be done by not specifying the :switches or :strict option.
Yet that's clearly false, since earlier in the docs it said
:switches - defines some switches and their types. This function still attempts to parse switches that are not in this list.
and that was the crucial part we missed for a while.
After all this confusion was settled, the general sentiment seemed to be that OptionParser's dynamic mode was something of a misfeature, or at least, probably shouldn't be enabled by default, since it creates extremely unpredictable behaviour based on the atoms that are currently loaded. (For reference, a brand new `
elixir` command, with zero extra code loaded, has about 10,000 atoms in play. This includes common options like `
:help`, etc.)
I was asked to bring this up on the mailing list, so here I am. Personally, while I can see the utility of this mode for someone who really doesn't care about invalid arguments, the fact is that "not `
:strict`" basically means "incredibly arbitrary". I mean, surely this doesn't seem like sensible default behaviour:
iex(1)> OptionParser.parse(["--foo", "--bar"], switches: [a: :string, b: :boolean])
{[], [], [{"--foo", nil}, {"--bar", nil}]}
iex(2)> :foo
:foo
iex(3)> OptionParser.parse(["--foo", "--bar"], switches: [a: :string, b: :boolean])
{[foo: true], [], [{"--bar", nil}]}
iex(4)> :bar
:bar
iex(5)> OptionParser.parse(["--foo", "--bar"], switches: [a: :string, b: :boolean])
{[foo: true, bar: true], [], []}
And yes, this can be avoided by reading the docs more closely (aside from the documentation error quoted above), but I wonder if we should be defaulting to the less surprising, less hair-pulling, less "why/how the heck is it doing that?!" option instead?