
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