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) _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe