[Haskell-cafe] APIs

Benjamin Franksen benjamin.franksen at bessy.de
Tue May 24 18:39:01 EDT 2005


On Tuesday 24 May 2005 20:31, robert dockins wrote:
> >> > One of the best bad example is the use of boolean as arguments.
> >>
> >> Oh, yes.  That's a pet peeve of mine.  About 99% of boolean
> >> arguments should be meaningful two-valued enumerated types.  It's
> >> literally a one-liner to create such an enumerated type, so
> >> there's no excuse.
> >
> > The documentation effect and type safety provided by two-valued
> > enumerated types is indeed much greater. But one needs a conversion
> > from Bool to the enumeration if one wants to pass the result of a
> > logic operation to the function. What about records with named
> > fields, especially if more options are needed?
> >
> > data CreateDirectoryOptions = Cons {createParents :: Bool}
> >
> > createDirectory (Cons {createParents = True}) "dir"
>
> Hummmm.... I think I like this.  Something like the following allows
> a simple way to make the call site concise and provide defaults at
> the same time.  Additional plus -- adding options requires no
> call-site code changes.
>
> ---------------------------------------
>
> import Control.Monad
>
> -- provide "opts" which should be a labeled record
> -- of default options
> class FunctionOptions a where opts :: a
>
> -- alias for readability when you don't want to change
> -- the default options
> defaults = opts
>
> -- create a datatype for each function which needs some flags
> data CreateDirectoryOptions
>     = CreateDirectoryOptions
>      { createParents :: Bool
>      , barFlag :: Bool
>      , andNow :: Bool
>      , aWholeBunch :: Bool
>      , ofOther :: Bool
>      , seldomEverUsed :: Bool
>      , esotericOptions :: Bool
>      }
>
> -- set the flag defaults
> instance FunctionOptions CreateDirectoryOptions
>    where opts =
>            CreateDirectoryOptions
>            { createParents = False
>            , barFlag = True
>            , andNow = True
>            , aWholeBunch = True
>            , ofOther = False
>            , seldomEverUsed = True
>            , esotericOptions = False
>            }
>
> createDirectory :: CreateDirectoryOptions -> FilePath -> IO ()
> createDirectory o path =
>          do when (createParents o) (putStrLn "creating parents")
>             when (barFlag o) (putStrLn "bar flag true")
>             putStrLn ("creating "++path)
>             return ()
>
> -- readable AND typesafe :-)
> main = do createDirectory opts{ createParents = True } "foo"
>            createDirectory defaults "baz"

I agree that the results is quite nice at the call site. OTOH, the API 
gets more complicated. More names in an API make it harder to master, 
in my experience. Also, all those record tags are not reusable and thus 
will increase the likelyhood of name conflicts. I think readability of 
code using an API must be weighted against proliferation of names by 
the API, especially when those names have only a very limited scope of 
use (e.g. only in connection with one function). Thus, I would 
recommend to use this technique with care.

It would be best if Haskell would support (optional) named arguments in 
such a way that the names introduced are not global entities but 
limited to the function that declares them, so that two functions can 
use the same argument labels for different meanings without having to 
globally pre-declare these common labels. That is, labels would not be 
first-class. [This is in contrast to Haskell solutions based on 
advanced type hackery (I remember a post by Oleg where argument labels 
where re-usable but still global).] I admit that I have no idea how a 
decent syntax (not to speak of semantics) for such a feature should 
look like.

BTW, I always found it cool how named args are done in Life: declaration 
and application without labels like f(a,b,c) is intepreted as a 
shorthand for f(1=>a, 2=>b, 3=>c), i.e. numerical labels in their 
natural order. But you can also declare functions with other labels or 
even mix them with numerical labels (implicit or explicit): non-labeled 
arguments implicitly get ascending numbers (1,2,3,...) as labels.

Ben


More information about the Haskell-Cafe mailing list