Re: [Haskell-cafe] Re: Avoiding boilerplate retrieving GetOpt cmd line args

2007-07-27 Thread Stuart Cook
On 7/27/07, Eric Y. Kow [EMAIL PROTECTED] wrote:
 Solution #3 No lists, just records (lhs2TeX)
 --

 Advantages:
   very convenient/compact; have to write
 (i)   Flag type
 (ii)  Settings record type/GetOpt in one go
 (iii) default Settings
   easy to lookup flags

 Disadvantages:
   Not as flexible
- can't group flags into blocks and have different programs that use
  different subsets of flags (without sharing the same Setting type)
- everything must go into Settings
- seems harder to say stuff like 'if flag X is set and flag Y are in
  the list of Flags, then parameterise flag Z this way' or
  'flags X and Y are mutually exclusive'

This is what I'm using for my current project. Most of the
disadvantages don't apply in my case, because all my flags are
largely-independent simulation parameters.

The one thing I find annoying, though, is that for each option I add,
I need to make changes in three places:

  1) The definition of my options record
  2) My default options value
  3) My list of GetOpt.OptDescr

What I'd really like to be able to do is specify the field name, field
type, and GetOpt info in a single place, without any redundancy. This
is obviously impossible in vanilla Haskell, so some kind of fancy
preprocessing or templating would be necessary. (Sadly, I'm not in a
position to pull this off right now.)


Stuart
___
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe


[Haskell-cafe] Re: Avoiding boilerplate retrieving GetOpt cmd line args

2007-07-27 Thread Eric Y. Kow
Hi,

Here is a possible two part response.  Not literate code, just using 
to distinguish code from everything else.

A short answer
==
  getFilter = getString f Markdown.pl
  where f (Filter s) = Just s
f _ = Nothing
 
  getDateFormat = getString f %B %e, %Y
  where f (DateFormat s) = Just s
f _ = Nothing

For starters, you could squish these down into something like

 flagToString :: Flag - Maybe String
 flagToString (Filter s) = Just s
 flagToString (DateFormat s) = Just s
 ...
 flagToString _ = Nothing
 
Then you would have
 getFilter = getString flagToString Markdown.pl
 getDateFormat = getString flagToString %B %e, %Y
 
A long answer
=
I have noticed a lot of ways of dealing with GetOpt flags in Hakell
programs and thought it might be useful to catalogue them.  A lot of
this could be wrong btw, for example, advantages/disadvantages.  But I
think the general idea might be useful, so please add to this if you
see other solutions.

Solution #1 Ginormous record

Do you happen to have some giant recordful of command line parameters?
Something like

  data Settings = Settings { filter :: Maybe String
   , dateFormat :: Maybe String
   , blahBlah   :: Maybe Blah
   ...
   , thisIsGetting :: RatherLargeIsntIt
   }
  
  emptySettings :: Settings
  emptySettings = Settings { filter = Nothing
   , dateFormat = Nothing 
   }
 
  toSettings :: [Flag] - Settings
  toSettings fs = toSettingsH fs emptySettings
  
  toSettingsH :: [Flag] - Settings - Settings
  toSettingsH (Filter s:fs) i = toSettingsH fs (i { filter = s })
  toSettingsH (DateFormat s:fs) i = toSettingsH fs (i { dateFormat = i })
 
Note: You can make this a little less painful by factoring out the
recursion (took me a while to realise this!).

  toSettings fs = foldr ($) emptySettings (map processFlag fs)
 
  processFlag :: Flag - Settings - Settings
  processFlag (Filter s) i = i { filter = Just s }
  processFlag (DateFormat s) i = i { dateFormat = s }
  ...

Advantages:
  - simple, easy to look up settings

Disadvantages:
  boring; have to write
(i)   Flag type
(ii)  Settings record type
(iii) default Settings 
(iv)  processFlag entry
(v)   GetOpt entry

  record gets really really huge if you have a lot of flags

Solution #2 List of flags (darcs) 
-
Don't bother keeping any records around, just pass around a big list of
flags to functions that depend on settings.

if the flag has any parameters, you can't just write (DateFormat
`elem` fs); you'll have to write some boilerplate along the lines
of

 hasDateFormat :: [Flag] - Bool
 hasDateFormat (DateFormat s:fs) = True 
 hasDateFormat (_:fs) = hasDateFormat fs
 hasDateFormat [] = False 
 
 getDateFormat :: [Flag] - Maybe String
 getDateFormat (DateFormat s:fs) = Just s
 getDateFormat (_:fs) = getDateFormat fs
 getDateFormat [] = Nothing
 
which again can be factored out...

 fromDateFormat :: Flag - Maybe String
 fromDateFormat (DateFormat x) = Just x
 fromDateFormat _ = Nothing
 
 hasDateFormat fs = any (isJust.fromDateFormat) fs
 getDateFormat fs = listToMaybe $ mapMaybe fromDateFormat fs

Still, this is more pay-as-you-go in the sense that not all flags need
to be accessed, so maybe you end up writing less boilerplate overall

Advantages:
  simple
  very convenient to add flags (as a minimum, you have to write
(i)   flag type
(ii)  GetOpt entry
(iii) lookup code (but pay-as-you-go)

Disadvantages:
  still a bit boilerplatey

Solution #3 No lists, just records (lhs2TeX)
--
This one is due to Andres Löh, I think although my rendition of it may
not be as nice as his.

Ever considered that your Settings record could almost be your Flag
type?  The trick here is recognising that constructors are functions too
and what GetOpt really wants is just a function, not necessarily a
constructor.

 type Flag a = (a - Settings - Settings)
 
 options :: [OptDescr Flag]
 options =
   [ Option f [filter]
   (ReqArg (\x s - s { filter = Just x }) TYPE)
   blahblah
   , Option d [date-format]
   (ReqArg (\x s - s { dateFormat = Just x }) TYPE)
   blahblah
 
   ]

Advantages:
  very convenient/compact; have to write
(i)   Flag type
(ii)  Settings record type/GetOpt in one go
(iii) default Settings 
  easy to lookup flags
  
Disadvantages:
  Not as flexible
   - can't group flags into blocks and have different programs that use
 different subsets of flags (without sharing the same Setting type)
   - everything must go into Settings
   - seems harder to say stuff like 'if flag X is set and flag Y are in
 the list of Flags, then parameterise flag Z this way' or
 'flags X and Y are mutually 

[Haskell-cafe] Re: Avoiding boilerplate retrieving GetOpt cmd line args

2007-07-27 Thread Dave Bayer
Neil Mitchell ndmitchell at gmail.com writes:

 then lookup, instead of just  as the else clause.

Thanks, all. After digesting what was on this thread as I woke up this
morning, I ended up writing something rather close to this.

I have a reusable wrapper around System.Console.GetOpt that adds

 type Opt a = (a,String)
 
 noArg :: a - ArgDescr (Opt a)
 noArg x = NoArg (x,)
 
 reqArg :: a - String - ArgDescr (Opt a)
 reqArg x s = ReqArg f s
 where f y = (x,y)
 
 optArg :: a - String - ArgDescr (Opt a)
 optArg x s = OptArg f s
 where f (Just y) = (x,y)
   f Nothing  = (x,)
 
 isOption :: Eq a = a - [Opt a] - Bool
 isOption opt assoc =  case lookup opt assoc of
 Nothing - False
 Just _  - True
 
 getOption :: Eq a = a - [Opt a] - String
 getOption opt assoc = case lookup opt assoc of
 Nothing - 
 Just s  - s

Then in a project-specific module I write

 data Flag
 = Filter
 | DateFormat
 | DocStart
 | DocEnd
 | ForceStyle
 | Help
 deriving (Eq)
 
 defaults :: [Opt Flag]
 defaults =
 [ (Filter, Markdown.pl)
 , (DateFormat, %B %e, %Y)
 , (DocStart,   ^\\s*{-\\s*$)
 , (DocEnd, ^\\s*-}\\s*$)
 ]
 
 flags :: [OptDescr (Opt Flag)]
 flags =
 [ Option ['s'] [style]  (noArg ForceStyle)
 Overwrite existing style.css
 , Option ['m'] [markup] (reqArg Filter path)
 Path to Markdown-style markup filter
 , Option ['d'] [date]   (reqArg DateFormat format)
 Unix-style modification date format
 , Option ['a'] [start]  (reqArg DocStart string)
 Documentation start string
 , Option ['b'] [end](reqArg DocEnd string)
 Documentation end string
 , Option ['h'] [help]   (noArg Help)
 Print this help message
 ]

which looks almost like the sample code I started with. Reading quickly,
one might miss the case change from `NoArg` to `noArg`, etc.

This is simple, and it works, with less option-specific boilerplate. One
could imagine generating `flags` automatically from an extension of
`defaults`, but I'm content to move on.

The relevant code is at

http://www.math.columbia.edu/~bayer/Haskell/Annote/GetOpt.html
http://www.math.columbia.edu/~bayer/Haskell/Annote/Flags.html
http://www.math.columbia.edu/~bayer/Haskell/Annote/Main.html


___
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe


[Haskell-cafe] Re: Avoiding boilerplate retrieving GetOpt cmd line args

2007-07-27 Thread Eric Y. Kow
To anyone who followed up on this thread (hi!).  I have posted the
GetOpt-summary part of my message on the wiki:

   http://www.haskell.org/haskellwiki/GetOpt

Please update it with the relevant parts of your followups, and correct
any silliness.  Haven't had the time to look, but I'm particularly
interested in what Johnathan suggested because (at a glance), it seems
far less clumsy than my solution #4.  As usual, don't hesitate to remove
things from this page, rename it, etc.

-- 
Eric Kow http://www.loria.fr/~kow
PGP Key ID: 08AC04F9 Merci de corriger mon français.


pgpBQ884kv2Eb.pgp
Description: PGP signature
___
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe