[Haskell-cafe] Correspondence between libraries and modules
wren ng thornton
wren at freegeek.org
Wed Apr 25 06:57:45 CEST 2012
On 4/23/12 3:06 PM, Alvaro Gutierrez wrote:
> I see. The first thing that comes to mind is the notion of module
> granularity, which of course is subjective, so whether a single module or
> multiple ones should handle e.g. doubles and integrals is a good question;
> are there guidelines as to how those choices are made?
I'm not sure if there are any guidelines per se; that's more of a
general software engineering problem. If you browse around on Hackage
you'll get a fairly good idea what the norms are though. Everyone seems
to have settled on a common range of scope--- with notable exceptions
like the containers library with far too many functions per module, and
some of Ed Kmett's work on category theory which tends towards very few
declarations per module.
> At any rate, why do these modules, with sufficiently-different
> functionality, live in the same library -- is it that they share some
> common bits of implementation, or to ease the management of source code?
I contacted Don Stewart (the former maintainer) to see whether he
thought I should release the integral stuff on its own, or integrate it
into bytestring-lexing. We agreed that it made more sense to try to
build up a core library for lexing various common data types, rather
than having a bunch of little libraries. He'd just never had time to get
around to developing bytestring-lexing further; so I took over.
Eventually I plan to add rendering functions for floating point, and to
split up the parsers for different floating point formats, so that it
more closely resembles the integral stuff. But that won't be until this
fall or later, unless someone requests it sooner.
 Having an omni-parser can be helpful when you want to be liberal
about your input. But when you're writing parsers for a specified
format, usually they're not that liberal so we need to offer restricted
lexers in order to give code reuse.
>> When dealing with FFI code, because of the impedance mismatch between
>> Haskell and imperative languages like C, it's clear that there's going to
>> be some massaging of the API beyond simply declaring FFI calls. As such,
>> clearly we'd like to have separate modules for doing the low-level binding
>> vs presenting a high-level API. Moreover, depending on what you're
>> interfacing with, you may be forced to have multiple low-level modules.
> Ah, that's a good use case. Is the lower-level module usually made "public"
> as well, or is it only an implementation detail?
Depends on the project. For ByteStrings, most of that is hidden away as
implementation details. For binding to C libraries, I think the current
advice is to offer the low-level interface so that if there's something
the high-level interface can't handle well, people have some easy recourse.
>> On the other hand, the main purpose of packages or libraries is as unit of
>> distribution, code reuse, and separate compilation. Even with the Haskell
>> culture of making small libraries, most worthwhile units of
>> distribution/reuse/compilation tend to be larger than a single
>> namespace/concern. Thus, it makes sense to have more than one module per
>> package, because otherwise we'd need some higher level mechanism in order
>> to manage the collections of package-modules which should be considered a
>> single unit (i.e., clients will almost always want the whole bunch of them).
> This is the part that I'm trying to get a better sense of. I can see how in
> some cases, it makes sense for more than one module to form a unit, because
> they are tightly coupled semantically or implementation-wise -- so clients
> will indeed want the whole bunch. On the other hand, several libraries
> provide modules that are all over the place, in a way that doesn't form a
> "unit" of any kind (e.g. MissingH), and it's not clear that you would want
> any Network stuff when all you need is String utilities.
Yeah, MissingH and similar libraries are just grab-bags full of stuff.
Usually grab-bag libraries think of themselves as place-holders, with
the intention of breaking things out once there's something of a large
enough size to warrant being its own package. (Whether the breaking out
actually happens is another matter.) But to get the general sense of
things, you should ignore them.
Instead, consider one of the parsing libraries like uu-parsinglib,
attoparsec, parsec, frisby. There are lots of pieces to a parsing
framework, but it makes sense to distribute them together.
Or, consider one of the base libraries for iteratees, enumerators,
pipes, conduits, etc. Like parsing, these offer a whole framework. You
won't usually need 100% of it, but everyone needs a different 80%.
Or to mention some more of my own packages, consider stm-chans,
unification-fd, or unix-bytestrings. In unification-fd, the stuff
outside of Control.Unification.* could be moved elsewhere, but the stuff
within there makes sense to be split up yet distributed together. For
stm-chans because of the similarity in interfaces, use cases, etc, it'd
be peculiar to want to separate them into different packages. In
unix-bytestring I separated off the Iovec stuff (FFI implementation
details) from the main API, but clearly they must go together.
> But the way you describe it, it seems that despite centralization having
> those disadvantages, it is more or less the way the system works, socially
> (egos, bad form, etc.) and technically (because of the lack of compiler
There's a difference between centralization and communalization.
With centralization there's a central authority who makes all the rules
and (usaully) enforces them. This is the benevolent dictator model
common in open-source. The problem is: what do you do if the dictator
goes missing (gets hit by a bus, is too busy this semester, etc)?
With communalization, there's no central authority that writes/enforces
the laws; instead, the community as a whole will come to agree on the
norms. This is the way societies often operate (i.e., societies as
cultures, rather than as governments). In virtue of the social
interaction, things come to be a particular way, but there isn't
necessarily any person or committee that decided it should be that way.
Moreover, in order to disrupt the norms it's not enough to dispose of a
dictator; you need some wide-scale way of disrupting the network of
social interaction. The problem here is that it can be very hard to
steer a community. If you've identified a problem, it's not clear how to
get it fixed (whereas a dictator could just issue a fiat).
In practice, every organization has a bit of both models; it's just a
question of how much of each, and in what contexts. The Haskell
community is more centralized when it comes to things like the Haskell
Report and the Haskell Platform, because you really need it there.
Whereas Hackage and the Cafe are more of your standard social community.
> except that it is ad-hoc instead of mechanically enforced. In
> other words, I don't see what the advantages of allowing ambiguity
> currently are.
If you mechanically enforce things then you will find clashes. That's
not the problem: clashes exist, you find them, whatever. The problem is:
now that you've found it, how are you going to resolve it?
You can't just make Hackage refuse packages which would cause a module
name conflict. If you try then you'll get angry developers who just
leave or who badmouth Haskell (or both), which does no good for anyone.
You have to have an escape hatch, some way for people to raise
legitimate issues such as "the conflictor hasn't been maintained in five
years and has no users", or "I wrote the old package and this new
package is meant to supersede it", etc. But now you need to have a group
of people who work on resolving those issues and making those
case-by-case decisions about how conflicts should be resolved.
Allowing clashes saves you from needing that group of people. If you
allow clashes, there are no developer complaints to be resolved. A lot
of resources are tied up in making those central authority groups, and
by not having such a central authority we free up those resources to be
In cases like Perl's CPAN and Linux distros, they have enough resources
that they can afford the overhead cost to create and maintain such
groups. In addition, they're large enough that the resources for that
group doesn't necessarily diminish the resources for other things. E.g.,
some members of the Linux developer community are no good at
programming, but they're great at social organization. If you have a
central authority group, they can contribute to that and thereby provide
resources; vs, if there's no such group, they're unlikely to offer
programming time or other resources instead.
Whereas for small communities: overhead costs are higher proportionally,
and small communities aren't able to gather as many resources to cover
them. In addition, the person who could offer social organization is
probably already offering other resources which she wouldn't be able to
offer if she moved over to helping the central authority; so you're
closer to a zero-sum game of needing to decide how to allocate your
> Ah, interesting. So, perhaps I misunderstand, but this seems like an
> argument in favor of having uniquely-named modules (e.g. Foo.FD and
> Foo.TF) instead of overlapping ones, right?
I mean, ideally I'd like to see GHC retooled so that both fundeps and
type families actually compile down to the same code, and one is just
sugar for the other (or both are sugar for some third thing). Then we'd
get rid of the real problem of there being multiple incompatible ways of
doing the same thing. Until then, it's probably better to just pick one
approach for each project, rather than trying to maintain parallel forks
for each approach. But if you're going to maintain parallel forks, then
it's probably best to not do the module punning thing.
More information about the Haskell-Cafe