[Haskell-cafe] Re: Unexported functions are evil

ajb at spamcop.net ajb at spamcop.net
Tue May 17 22:20:47 EDT 2005


G'day all.

Quoting Peter Simons <simons at cryp.to>:

>  >> 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


More information about the Haskell-Cafe mailing list