Continuing the discussion from Simple newbie-friendly CLI parser?:
Let me try to explain why I think cmdargs
subverts Haskell. The package has a few different interfaces, but I’m talking about the implicit interface from the module System.Console.CmdArgs.Implicit
. The interface allows you to write a completely normal data type for your command line options, for example:
data Sample = Sample {hello :: String} deriving (Show, Data)
The only special thing is that you need to derive the Data
class which allows the package to inspect the structure of the data type generically (see the documentation in Data.Data).
The magic of cmdargs
happens in the way you specify the behavior of the command line arguments. The way you do that is by defining a value of your command line options data type, but with magic annotations, for example:
sample = Sample{hello = "" &= help "World argument" &= opt "world"}
&= summary "Sample v1"
Here we are assigning an annotated string value to the hello
field of our Sample
data type. But String
in Haskell is just a list of characters. It should not be possible to add extra annotations to a String
. So where do the annotations come from? Or rather, where do the annotations go?
The answer is that it uses unsafePerformIO
to write the annotations to a global mutable variable. It writes this information in such a way that it is possible to recover the structure of the data type from the final result, so it is possible to recover the information about which annotation applies to which part of the options data structure.
There are some big problems with this approach as the documentation notes:
Values created with annotations are not pure - the first time they are computed they will include the annotations, but subsequently they will not.
Even using this scheme, sometimes GHC’s optimisations may share values who have the same annotation. To disable sharing you may need to specify
{-# OPTIONS_GHC -fno-cse #-}
in the module you define the flags.
So you need to stick very closely to the examples and even then you might need to ask the compiler not to interfere.