
Please pardon the tendentious subject, but I felt like making a clear statement. ;-) I was wondering: Is there any reason why you would have a function in one of your modules and _not_ export it? I ask because I have _never_ had problems with a module exporting too much, but I have had problems with modules exporting too little quite frequently. The reason why I like purely functional languages like Haskell is that it is virtually impossible to write code that cannot be reused. So why would you exclude other modules from reusing you code? The only reason I could think of is that a function is considered to be "internal", meaning: You don't want users of the module to rely on the function still being there (or still working the same way) in any of the next revisions. On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there? Then those people who enjoy playing with fire _can_ use it, and everybody else will not. Is there something I missed? Peter

Peter Simons
The only reason I could think of is that a function is considered to be "internal", meaning: You don't want users of the module to rely on the function still being there (or still working the same way) in any of the next revisions.
Right. (I guess you could sometimes have name clashes as well?) You could also view if from the opposite angle: when I work on a module, it is nice to check the export list to see what parts I need to maintain consistently, and what parts I am at liberty to change.
On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there?
So you don't want to automatically re-export imports, I take it? :-)
Then those people who enjoy playing with fire _can_ use it, and everybody else will not.
Is there something I missed?
An opportunity to ignore export restrictions would be very useful for testing. It is nice to put tests into a separate module, but sometimes you really need access to "internals" as well. -kzm -- If I haven't seen further, it is by standing in the footprints of giants

Thanks for your opinions everybody! Ketil Malde writes:
I guess you could sometimes have name clashes as well?
I was afraid about those for the longest time too, but in practice name clashes curiously enough hardly ever occur -- in my experience. The problem only arises when you actually _use_ a function that's ambiguous, just importing two modules with a function called 'foo' in them is no problem. And then you still have the option of using 'hide' or 'qualified' when importing the modules.
On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there?
So you don't want to automatically re-export imports, I take it? :-)
No. ;-) Although I would like to have a shortcut for saying "(re-)export everything". David Roundy writes:
[...] why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there?
Because a separate module is more work.
It sure is, but I don't think that "it's more work" or "it's less work" is a good principle by which to make software design decisions. If you follow this idea through, then you could also argue that splitting a program into separate functions is more work than writing one big, big 'main' function for the task. And it sure is. Still enlightened programmers do just that, because it often turns out that doing so is _less work_ in the long run. I believe the same applies to cleanly grouping functions into separate modules, and a separation between "public API" and "internal implementation" doesn't sound so unreasonable, IMHO.
Almost every module I write has private functions, and I'd really not want to write twice as many modules.
Why do these functions need to be private?
In darcs, if someone needs to "play with fire", he can do it right there in the module itself, and export a new interface function.
Not really, unless you decide to include the patches into the main distribution. If you don't, someone who wants to access the internals essentially has to create a fork of your software, and that's something you really want to avoid if you want to encourage re-use.
In the case of darcs, I'd say that the whole point of using modules (besides making things faster to compile) is to place these barriers, so that one can modify an individual module without worrying about the rest of the code, as long as one keeps the interface fixed.
I'm not sure whether I understand that. When modifying a function 'foo', why do you have to worry _less_ about 'bar' if it is in a separate module than you'd have to if it would be in the same module?
There's also the ease of bug-writing issue. I think that exported interfaces should be the sorts of functions where the meaning of the function can be guessed from its name and type.
Shouldn't _any_ function have those properties if at all possible? ajb writes:
Is there any reason why you would have a function in one of your modules and _not_ export it?
Because that function is nobody else's business.
I'm sorry, but that's not really a convincing technical argument, that's essentially "because I want it so".
So while I think you've identified a real problem (the modules that you want to use expose insufficient APIs), I think your solution is wrong. The right solution is to complain to the module writer, and ask them to export a functionally complete API.
So my solution is wrong and your solution is right. ;-) Having that out of the way, what are your reasons for this opinion? (Other than that the "art of programming" says it ought to be this way.)
The only reason I could think of is that a function is considered to be "internal" [...]
Right. And I agree with David: This is reason enough.
How is an internal function any _more_ internal if you don't export it? How is it less internal if you _do_ export it? Why doesn't the approach -- | /Attention:/ this function is internal and may change -- at random without even so much as shrug. foo = ... suffice?
With my business hat on: Every time you expose something for use, you must at the very least document it.
I'd recommend documenting _all_ functions I write, not just the exported ones.
Taking this to an illogcial extreme, why don't we allow pointer arithmetic in Haskell, but require people to import "Prelude.C" first, so that people who enjoy playing with fire can do it?
You mean "Foreign.Ptr"? Curiously enough, if Haskell wouldn't support pointer arithmetic, the language would be completely useless to me, so I for one don't think that's taking things to the illogical extreme. Iavor Diatchki writes:
[...] in practice this is likely to often lead to recursive modules [...]
Why is that? My intuition would say that the exact opposite is true: a more fine-grained set of modules is _less_ likely to require recursive modules. But that's just intuition. Do you have an concrete example which illustrates this point? Peter

Hello,
On 17 May 2005 12:09:35 +0200, Peter Simons
Iavor Diatchki writes:
[...] in practice this is likely to often lead to recursive modules [...]
Why is that? My intuition would say that the exact opposite is true: a more fine-grained set of modules is _less_ likely to require recursive modules. But that's just intuition. Do you have an concrete example which illustrates this point?
The smaller the modules are the more likely it is that they will end up being recursive. To see that, consider the extreme where every function is in a separate module, then all recursive groups of functions will end up being recursive groups of modules. As a more practical example consider a file A.hs that defines some data type T and exports a function "f" that is defined in terms of a private function "g". Now if we place "g" in a file called "Private.hs" then A needs to import Private, but also "Private" needs to import "A" for the definition of "T". -iavor

Iavor Diatchki writes:
Do you have an concrete example which illustrates this point?
[...] consider a file A.hs that defines some data type T and exports a function "f" that is defined in terms of a private function "g". Now if we place "g" in a file called "Private.hs" then A needs to import Private, but also "Private" needs to import "A" for the definition of "T".
Ah, now it see it! Great example. But couldn't you just put T in "Foo/Base.hs", g in "Foo/Private.hs", then create "Foo/API.hs" which imports "Foo/Base.hs" and "Foo/Private.hs", and leave f in "A.hs" and import "Foo/API.hs"? Peter

G'day all.
Quoting Peter Simons
Is there any reason why you would have a function in one of your modules and _not_ export it?
Because that function is nobody else's business.
I'm sorry, but that's not really a convincing technical argument, that's essentially "because I want it so".
Imagine, for a moment, just how much is involved in moving the cursor on your screen every time you move your mouse. Think about it. From the hardware in the mouse, to the CPU, the operating system, the windowing environment, the graphics card, the monitor... It's hugely complex. Probably more complex than most people, including experts, can take in all at once, and yet it fits on your desk or your lap. The reason why it all works is that everyone sticks to their interfaces and doesn't interfere in stuff that is not their business. The people who build the mouse don't worry about what is on the other end of the serial/PS2/USB port, they just work to the published protocol. The people who write the operating system don't concern themselves with semiconductor physics. The people who write the driver for the graphics card don't concern themselves with what's behind the system calls that they use. They do this because if they don't, computers become an unmanageable, fragile mess, and they know it. Private functions are not there just because the author hates you and doesn't want you to have any fun. It's because if you don't stick to the public interface, we all lose. Nobody wants their computers to fail unexpectedly, and if you've ever complained about it happening, then you of all people should want to avoid contributing to the problem. "But", I hear you object, "it's not like I'm building large applications for sale or widescale distribution; this is only for my personal research and/or amusement." This is perfectly reasonable, but as a library author, I still have to ask myself which group I should optimise for: the people who care about robustness and safety, or the people who don't. While Haskell has a wide potential audience, there are an awful lot of people who use declarative languages precisely because they _do_ care about robustness and safety. It's one of the major selling points. And I'm afraid that outweighs your desire to hack. Sorry. :-)
So while I think you've identified a real problem (the modules that you want to use expose insufficient APIs), I think your solution is wrong. The right solution is to complain to the module writer, and ask them to export a functionally complete API.
So my solution is wrong and your solution is right. ;-)
Yup. But it's not just my opinion. I have a lot of engineering experience (the overwhelming majority of it not mine personally) to back me up.
Having that out of the way, what are your reasons for this opinion? (Other than that the "art of programming" says it ought to be this way.)
Perhaps rather than the "art of programming", you should be reading "The Mythical Man Month", or "The Psychology of Computer Programming". If you need to do something that badly, then it's worth doing it right. Doing it wrong is not engineering, it's hackery. And hackery almost always comes back to haunt you. (It's "almost always" because sometimes you've moved on and instead it come back to haunt the _next_ poor sod who has to maintain your code. "Almost always", the next poor sod is you.) Most engineers know this by bitter experience. To use XP language, "do the smallest thing that could possibly work" only works in conjunction with "aggressively refactor".
The only reason I could think of is that a function is considered to be "internal" [...]
Right. And I agree with David: This is reason enough.
How is an internal function any _more_ internal if you don't export it? How is it less internal if you _do_ export it?
If it's not exported, you can GUARANTEE that nobody outside your module is using it. So the cost of changing it is limited to only that which is inside the module.
Why doesn't the approach
-- | /Attention:/ this function is internal and may change -- at random without even so much as shrug.
foo = ...
suffice?
Because someone will use it anyway, and complain when their code breaks. That someone will be on your team, and will hold up shipping YOUR product because if it, and you will be blamed. By the way: The goal of an engineer, according to Dilbert, is to get through their career without being blamed for a major failure. So modularise your code. :-) By the way: very often, a function changes in such a way that it doesn't change the type of the function. (It might change things which are encapsulated in a monad, for example.) So you might break their code, and they might not know about it. That's even worse.
With my business hat on: Every time you expose something for use, you must at the very least document it.
I'd recommend documenting _all_ functions I write, not just the exported ones.
There's developer documentation and there's user documentation. Developer documentation is what that you need to know to modify the internals. User documentation is what you need to know to use it from the outside. If you leak internal information to users, you increase the surface area of the module.
You mean "Foreign.Ptr"?
Foreign.Ptr only really works on foreign data, and requires you to go to serious amounts of trouble to use it. Actually, if you could use private functions from modules by prefixing the call with yesIKnowThisIsntKosher, then I might be happier with it.
Why is that? My intuition would say that the exact opposite is true: a more fine-grained set of modules is _less_ likely to require recursive modules. But that's just intuition. Do you have an concrete example which illustrates this point?
I think Iavor is right. If you have data types which are only used privately in some module, then those data types don't appear the interface of that module. So while it might not introduce recursive modules, it does increase the size of the module interface, introduce dependencies which need not be there, and generally make your program more complicated than it needs to be. Cheers, Andrew Bromage

On Sun, May 15, 2005 at 12:29:11PM +0200, Peter Simons wrote:
The only reason I could think of is that a function is considered to be "internal", meaning: You don't want users of the module to rely on the function still being there (or still working the same way) in any of the next revisions.
To me, this is a sufficient reason. The art of programming is the art of creating interfaces, and my feeling is that modules should export as little as possible, and every function exported should be carefully considered.
On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there? Then those people who enjoy playing with fire _can_ use it, and everybody else will not.
Because a separate module is more work. Almost every module I write has private functions, and I'd really not want to write twice as many modules. In darcs, if someone needs to "play with fire", he can do it right there in the module itself, and export a new interface function. But I guess perhaps you're thinking about "library" style modules? In the case of darcs, I'd say that the whole point of using modules (besides making things faster to compile) is to place these barriers, so that one can modify an individual module without worrying about the rest of the code, as long as one keeps the interface fixed. There's also the ease of bug-writing issue. I think that exported interfaces should be the sorts of functions where the meaning of the function can be guessed from its name and type. If you need to read the code of a function just to see what it does, you should be modifying the module rather than importing a function from it. -- David Roundy http://www.darcs.net

G'day all.
Quoting Peter Simons
I was wondering: Is there any reason why you would have a function in one of your modules and _not_ export it?
The short answer: Because that function is nobody else's business. The long answer: The reason why programming is hard is that programs are complex. The art of software design and construction is the art of controlling that complexity so that it doesn't get out of hand. Paraphrasing what David Roundy said, every piece of code (with the possible exception of main) that you write is an API for some other part. Controlling the complexity means that
I ask because I have _never_ had problems with a module exporting too much, but I have had problems with modules exporting too little quite frequently.
Continuing on from the previous thought, part of the problem here is that we teach people to write code (e.g. how to implement a sort), but we don't teach people to write APIs. APIs are both trickier to get right AND are more important in larger programs. So while I think you've identified a real problem (the modules that you want to use expose insufficient APIs), I think your solution is wrong. The right solution is to complain to the module writer, and ask them to export a functionally complete API.
The reason why I like purely functional languages like Haskell is that it is virtually impossible to write code that cannot be reused.
I'd disagree with that. Strongly. Very strongly. It's _easier_ to write reusable code, for sure. But reusable code is tricky no matter what you do. And according to Alan Perlis, you never know if it's truly reusable until you've reused it at least three times.
The only reason I could think of is that a function is considered to be "internal", meaning: You don't want users of the module to rely on the function still being there (or still working the same way) in any of the next revisions.
Right. And I agree with David: This is reason enough. With my business hat on: Every time you expose something for use, you must at the very least document it. At most, you are implicitly supporting it. If you have customers that have spent money or other resources on your code, then you owe it to them.
On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there? Then those people who enjoy playing with fire _can_ use it, and everybody else will not.
Taking this to an illogcial extreme, why don't we allow pointer arithmetic in Haskell, but require people to import "Prelude.C" first, so that people who enjoy playing with fire can do it? After all, we use it under the covers... Cheers, Andrew Bromage

On 15/05/05, ajb@spamcop.net
Taking this to an illogcial extreme, why don't we allow pointer arithmetic in Haskell, but require people to import "Prelude.C" first, so that people who enjoy playing with fire can do it? After all, we use it under the covers...
Isn't this called Foreign.Ptr? -- Sam

At 21:42 15/05/05 -0400, ajb@spamcop.net wrote:
Continuing on from the previous thought, part of the problem here is that we teach people to write code (e.g. how to implement a sort), but we don't teach people to write APIs. APIs are both trickier to get right AND are more important in larger programs.
I think you raise an important point. Reading this, I realize that I have no principled basis for deciding what makes a good API, in any language. I do, of course, have lots of personal ideas and feelings about what APIs should be like, but not that I could defend as coherent and rational strategy. So I ask myself: are there any good papers or books on this topic that outline a coherent and principled approach to API design? #g ------------ Graham Klyne For email: http://www.ninebynine.org/#Contact

I think you raise an important point. Reading this, I realize that I have no principled basis for deciding what makes a good API, in any language. I do, of course, have lots of personal ideas and feelings about what APIs should be like, but not that I could defend as coherent and rational strategy.
So I ask myself: are there any good papers or books on this topic that outline a coherent and principled approach to API design?
if you like the historical approach, you could start with David Parnas On the Criteria To Be Used in Decomposing Systems into Modules http://www.acm.org/classics/may96/ then look for papers that reference this one. see also the interview http://www.acm.org/sigsoft/SEN/parnas.html for how he came to start thinking about interfaces and information hiding. cheers, claus

G'day all.
Quoting Graham Klyne
I think you raise an important point. Reading this, I realize that I have no principled basis for deciding what makes a good API, in any language.
Me neither. Though I have short reading list. First off, this series of articles by Ken Arnold. They are a bit Java- centric, but it's all good: http://www.artima.com/intv/perfect.html http://www.artima.com/intv/taste.html http://www.artima.com/intv/distrib.html http://www.artima.com/intv/sway.html http://www.artima.com/intv/decouple.html http://www.artima.com/intv/issues.html Some of the modern C++ books like "Modern C++ Design" and "Generative Programming" are worth a skim in lieu of a decent book on how to deal with true parametric polymorphism and typeclasses. Oh, if you can get a copy of "Software Fundamentals" (the collection of Parnas papers), do it. It's a fabulous read, even if you don't get much in the way of API design out of it. Cheers, Andrew Bromage

At 19:39 18/05/05 -0400, ajb@spamcop.net wrote:
G'day all.
Quoting Graham Klyne
: I think you raise an important point. Reading this, I realize that I have no principled basis for deciding what makes a good API, in any language.
Me neither. Though I have short reading list.
First off, this series of articles by Ken Arnold. They are a bit Java- centric, but it's all good:
http://www.artima.com/intv/perfect.html http://www.artima.com/intv/taste.html http://www.artima.com/intv/distrib.html http://www.artima.com/intv/sway.html http://www.artima.com/intv/decouple.html http://www.artima.com/intv/issues.html
Looks interesting. Thanks.
Some of the modern C++ books like "Modern C++ Design" and "Generative Programming" are worth a skim in lieu of a decent book on how to deal with true parametric polymorphism and typeclasses.
In a similar vein, I think Martin Fowler's book on refactoring has some interesting insights. #g ------------ Graham Klyne For email: http://www.ninebynine.org/#Contact

On Wednesday 18 May 2005 11:58, Graham Klyne wrote:
So I ask myself: are there any good papers or books on this topic that outline a coherent and principled approach to API design?
Matthias Ettrich's talk at aKedemy 2004 about Qt API was interesting: http://ktown.kde.org/akademy/Matthias_Ettrich_Designing_Qt-style_APIs_audio.... http://ktown.kde.org/akademy/Matthias_Ettrich_Designing_Qt-style_APIs_video.... He emphasis that with a good API, you should not need to lookup the documentation to read and understand what a program does. One of the best bad example is the use of boolean as arguments. For an example taken from Sytem.Directory: createDirectoryIfMissing True "/tmp/foo/bar" What does this True means? Looking up the doc, it says "If the first argument is True the function will also create all parent directories if they are missing." Using specially crafted constructors can often easily improve this. i.e. : createDirectoryIfMissing CreateParent "/tmp/foo/bar" Oleg's keyword arguments [1] also seems a good tool for good APIs in Haskell. Cheers, Jérémy. [1] http://www.haskell.org/pipermail/haskell/2004-August/014416.html

G'day all.
Quoting Jérémy Bobbio
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. Cheers, Andrew Bromage

On Thu, 19 May 2005 ajb@spamcop.net wrote:
G'day all.
Quoting Jérémy Bobbio
: 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"

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"

On Tue, May 24, 2005 at 02:31:29PM -0400, 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"
I think it is easier just to declare it as Enum.. data ParentsFlag = DontCreateParents | CreateParents deriving(Enum) now (toEnum . fromEnum) will convert between ParentsFlag and Bool. John -- John Meacham - ⑆repetae.net⑆john⑈

On May 24, 2005, at 5:28 PM, John Meacham wrote:
[various ways of capturing options passed to functions discussed...]
I think it is easier just to declare it as Enum..
data ParentsFlag = DontCreateParents | CreateParents deriving(Enum)
now (toEnum . fromEnum) will convert between ParentsFlag and Bool.
Ah, but imagine I don't have the module source handy. Which corresponds to True and which to False? Mostly, I stay away from enumerations whose order matters, making a particular exception for Bool and Ord. If the names are (as above) DontDoSomething | DoSomething, using a newtype plus a boolean conveys the same sense without the forgetfulness: newtype CreateParents = CreateParents Bool createDirectory (CreateParents False) -Jan-Willem Maessen

On Wednesday 25 May 2005 00:11, Jan-Willem Maessen wrote:
On May 24, 2005, at 5:28 PM, John Meacham wrote:
[various ways of capturing options passed to functions discussed...]
I think it is easier just to declare it as Enum..
data ParentsFlag = DontCreateParents | CreateParents deriving(Enum)
now (toEnum . fromEnum) will convert between ParentsFlag and Bool.
Ah, but imagine I don't have the module source handy. Which corresponds to True and which to False?
Mostly, I stay away from enumerations whose order matters, making a particular exception for Bool and Ord.
If the names are (as above) DontDoSomething | DoSomething, using a newtype plus a boolean conveys the same sense without the forgetfulness:
newtype CreateParents = CreateParents Bool
createDirectory (CreateParents False)
Nice idea. Although this one also introduces additional global names, at least it is syntactically light-weight. BTW, how about putting all these different ideas on the Haskell wiki? Ben

Am Mittwoch, 25. Mai 2005 00:46 schrieb Benjamin Franksen:
On Wednesday 25 May 2005 00:11, Jan-Willem Maessen wrote:
On May 24, 2005, at 5:28 PM, John Meacham wrote:
[various ways of capturing options passed to functions discussed...]
I think it is easier just to declare it as Enum..
data ParentsFlag = DontCreateParents | CreateParents deriving(Enum)
now (toEnum . fromEnum) will convert between ParentsFlag and Bool.
Ah, but imagine I don't have the module source handy. Which corresponds to True and which to False?
Mostly, I stay away from enumerations whose order matters, making a particular exception for Bool and Ord.
If the names are (as above) DontDoSomething | DoSomething, using a newtype plus a boolean conveys the same sense without the forgetfulness:
newtype CreateParents = CreateParents Bool
createDirectory (CreateParents False)
Nice idea. Although this one also introduces additional global names, at least it is syntactically light-weight.
BTW, how about putting all these different ideas on the Haskell wiki?
Ben
What about a type synonym? I think type CreateParents = Bool wouldn't be a bad solution, you can directly use boolean operations and the name makes clear what True and False mean. Daniel

On Wednesday 25 May 2005 01:09, Daniel Fischer wrote:
Am Mittwoch, 25. Mai 2005 00:46 schrieb Benjamin Franksen:
On Wednesday 25 May 2005 00:11, Jan-Willem Maessen wrote:
On May 24, 2005, at 5:28 PM, John Meacham wrote:
[various ways of capturing options passed to functions discussed...]
data ParentsFlag = DontCreateParents | CreateParents deriving(Enum)
[more discussion...]
What about a type synonym? I think
type CreateParents = Bool
The thread started about readability at the call site. Using a type synonym for Bool needs to figure out what this "True" at call site means instead of understanding it right away. What about adding to the Daniel's definition: don'tCreateParents :: CreateParents don'tCreateParents = False createParents :: CreateParents createParents = True I'm not finding this fancy as the library user is not required to use them. Using constructors also emphasis which are options and which are not. Cheers, Jérémy.

Am Mittwoch, 25. Mai 2005 01:45 schrieb Jérémy Bobbio:
On Wednesday 25 May 2005 01:09, Daniel Fischer wrote:
What about a type synonym? I think
type CreateParents = Bool
The thread started about readability at the call site. Using a type synonym for Bool needs to figure out what this "True" at call site means instead of understanding it right away.
Quite, I didn't think of that yesterday.
What about adding to the Daniel's definition:
don'tCreateParents :: CreateParents don'tCreateParents = False createParents :: CreateParents createParents = True
I like that!
I'm not finding this fancy as the library user is not required to use them. Using constructors also emphasis which are options and which are not.
Cheers, Jérémy.
Cheers, Daniel

G'day all.
Quoting John Meacham
I think it is easier just to declare it as Enum..
data ParentsFlag = DontCreateParents | CreateParents deriving(Enum)
now (toEnum . fromEnum) will convert between ParentsFlag and Bool.
Except that it's still not clear whether True corresponds to CreateParents or not. I'd do it by providing functions with meaningful names, like this: createParentsIfTrue :: Bool -> ParentsFlag Cheers, Andrew Bromage

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

At 17:20 19/05/05 -0400, ajb@spamcop.net 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.
While I see your point in the case of Booleans, I note that, in other circumstances, not using a pre-existing data type can be sub-optimal; cf. use of Maybe noted at [1]. #g -- [1] http://www.ninebynine.org/Software/Learning-Haskell-Notes.html#UseStandardTy... ------------ Graham Klyne For email: http://www.ninebynine.org/#Contact

Hello,
On 15 May 2005 12:29:11 +0200, Peter Simons
On those occasions, however, why not put the function into a module, say "Foo.Bar.Private" and import it into "Foo.Bar" from there? Then those people who enjoy playing with fire _can_ use it, and everybody else will not.
Besides all the good points everyone else made, in practice this is likely to often lead to recursive modules which are not supported by most Haskell implementations. -Iavor
participants (16)
-
ajb@spamcop.net
-
Benjamin Franksen
-
Claus Reinke
-
Daniel Fischer
-
David Roundy
-
Graham Klyne
-
Graham Klyne
-
Henning Thielemann
-
Iavor Diatchki
-
Jan-Willem Maessen
-
John Meacham
-
Jérémy Bobbio
-
Ketil Malde
-
Peter Simons
-
robert dockins
-
Samuel Bronson