[Haskell-cafe] Re: Re: A better syntax for qualified operators?

Benjamin Franksen benjamin.franksen at bessy.de
Fri Sep 29 16:04:31 EDT 2006


Brian Hulley wrote:
> Benjamin Franksen wrote:
>> At the danger of becoming completely off-topic now (sorry!), I have
>> to say that I find /both/ versions ugly and unnecessarily hard to
>> read. My personal solution is to generally avoid qualified imports.
> 
> How does this solution scale? Surely it's only lucky if people happen to
> choose names that don't clash with those of other modules?

Many, if not most, name clashes result from two different things actually
being "the same concept". Such entities should be refactored into classes,
rather than disambiguated using qualified names, if possible. Apart from
these, a large class of name clashes is the result of chosing inappropriate
names. Chosing good names is an art in itself; and the more carefully you
name things, the less the likelyhood that they will accidentally clash with
somebody else's. For the remaining cases, you need to use qualified import;
I meant these cases when I said:

>> I use it only if absolutely necessary to disambiguate some symbol, and
>> then just for that symbol. I am aware that there is an opposing
>> faction here, who tries to convinve everyone that qualified import
>> should be the standard (and the actual exported symbols --at least
>> some of them-- meaningless, such as 'C' or 'T').
> 
> Although C and T are in themselves meaningless, the name of the module
> itself is not.

Ok, but I still don't like it. I would much rather use 'Set' than 'Set.T'.
Since it is always clear from the context whether something is a data type
or a class, the additional '.T' or '.C' only adds noise, making the program
uglier and harder to read.

BTW, it has been argued by others that a single exported data type or class
per module is a special case (however common) which cannot be generally
assumed for library modules. Even in the cmmon case where there is
one 'main' data type exported, you often need auxiliary data types (which
are often concrete types) to go along. I would find it strange to call main
concept just 'T' while the minor, auxiliary stuff gets a 'real' name.

> As I understand it, this convention makes the module name 
> carry the meaning so you use Set.T instead of Set.Set where the meaning is
> duplicated (a rather uneasy situation) in both the module name and type
> name.

Set.Set is horrible indeed. That is why I would rather not import all of Set
qualified. If there is a name clash, e.g. we also need some different data
type Set which gets exported from module Foo, then my solution is to
explicitly disambiguate Set, as in

 import qualified Foo (Set)
 import Foo hiding (Set)
 import qualified Set (Set)
 import Set hiding (Set)

 type FooBar = Foo.Set -- this one is often not necessary
 type Set = Set.Set

This buys me more readable functions at the cost of adding more noise at the
top of the module for disambiguating imports. I think the gain is worth the
cost, because the function definitions are where the non-trivial (i.e. hard
to understand) stuff is.

As to scalability: In practice I always wait for the compiler to complain
about ambiguous imports and only then fix like indicated above. A great
feature of the Haskell module system is that it /never/ allows any
ambiguity to actually appear in your source code. There is no way you could
accidentally use an imported entity when it is not absolutely clear (to the
compiler) which module it is imported from. OTOH, the compiler /only/
reports an error if you actually use such an ambigously imported name.
Otherwise the above method of 'manual' disambiguation would indeed be very
impractical.

>> I think such a
>> convention is inappropriate for a functional language (especially one
>> with user defined operators). There simply is no natural 1:1
>> correspondence between data type declaration and functions acting on
>> that data built into the language, as opposed to e.g. OO languages.
>> Extensibility in the functional dimension, i.e. the ability to
>> arbitrarily add functions that operate on some data without having to
>> change the code (module) that defines the data, is one of the
>> hallmarks of functional programming, as opposed to OO
>> programming.
> 
> If you have an abstract data type then it *is* like an object (though in a
> potentially more powerful way than in OOP) because there is no other way
> to manipulate values of that type.

Yes, right.

> If the type is not abstract, the advantage of calling it T is just that it
> avoids naming it twice (by type name and module name) in the situation
> where you want to not worry about name clashes with constructors of other
> types.

You never need to worry beforehand! You can rest assured that the compiler
will mercilessly flag all ambiguous uses of imported names. Only if and
ehen it does you need to start thinking about how you want to name these
different entities in your module.

[NB however far this discussion has digressed from its origin, there is
still a common theme here: I keep arguing against making life simpler for
the program /writer/ if this causes more difficulty for the
(human) /reader/ of a program. Here it is my argument that manually
resolving disambiguities may be more work initially, but (I think) it pays
off in more beautiful code that is easier to read (and therefore to
understand).]

>> However, nothing prevents us from offering two
>> interfaces (visible modules), one where the data type is abstract
>> ("client interface") and a different one where it is concrete ("extension
>> interface")
> 
> You can still call both types T... :-)

...and next come we name "the" function exported from each module 'f' or
what? Imagine

 set2 = Data.Set.Insert.f x set1

"What a beautiful world this could be..." ;-)) (*)

Cheers,
Ben
(*) Donald Fagen (forgot the name of the song)



More information about the Haskell-Cafe mailing list