5
votes

I know import qualified names has benefit of avoiding name conflicts. I'm asking purely from readability point of view.

Not familiar with haskell standard libraries, one thing I found annoying when reading haskell code (mostly from books and tutorials online) is that when I come across a function, I don't know if it belongs to a imported module or will be defined by user later.

Coming from a C++ background, it's usually seen as a good practice to call standard library function with namespace, for example std::find. Is it the same for haskell? If not then how do you overcome the problem that I mentioned above?

4
The problem in C++ is, that ADL can screw you over if you use unqualified calls. In Haskell, the worst you get is an ambiguity error, unless one library removes the function completely, while another adds it. In this case you'll likely get a type error - if not, the functions are most likely compatible anyways.Xeo

4 Answers

10
votes

From Haskell style guide:

Always use explicit import lists or qualified imports for standard and third party libraries. This makes the code more robust against changes in these libraries. Exception: The Prelude.

So, the answer is yes. Using qualified import is considered a good practice for standard and third party libraries except the Prelude. But for infix function with symbols (something like <|*|>) you may want to import it explicitly as qualified import doesn't look nice on it.

6
votes

I'm not too fond of qualified names, IMO they rather clutter the code. The only modules that should always be imported qualified are those that use names clashing with prelude functions – these normally have an explicit recommendation for doing so, in the documentation.

For widespreadly used modules such as Control.Applicative, there's not much reason not to import unqualified; most programmers should know all that's in there. For modules from less well-known packages that do something very specific, or to avoid clashes of a single name, you can use an explicit import list, e.g. import Data.List (sortBy), import System.Random.Shuffle (shuffleM) – this way, you don't have to litter your code with qualifiers, yet looking up an identifier in the imports section tells you immediately where it comes from (this is analogous to using std::cout;). But honestly, I find it even more convenient to just load the module into ghci and use

*ClunkyModule> :i strangeFunction

to see where it's defined.

There's one good point to be made about qualified imports or explicit import lists that I tend to neglect: they make your packages more future-proof. If a new version of some module stops exporting an item you need, or another module introduces a clashing name, then an explicit import will immediately point you to the problem.

2
votes

I feel the same as you. If I see functionName in a module I'm unfamiliar with then I have no idea which one of the many imports it comes from. "Unfamiliar module" here can also mean one I myself wrote in the past! My current style is like the following, but it's by no means universally accepted. Users of this style are probably in a very small minority.

import qualified Long.Path.To.Module as M

... use M.functionName ...

or if I want more clarity

import qualified Long.Path.To.Module as Module

... use Module.functionName ...

Very rarely I will fully qualify

import qualified Long.Path.To.Module

... use Long.Path.To.Module.functionName ...

However, I almost never qualify infix operators.

1
votes

My own set of rules.

1) Try not to import anything as qualified without renaming. B.ByteString is much more readable than Data.ByteString.ByteString. Exception can be made for truly ubiquitous modules, such as Control.Monad.

2) Don't import the whole module, import specific functions/types/classes, unless there are too many of them. That way if someone wants to find out where some function came from, he can just search the function's name in the beginning of the file.

3) Import closely related modules renaming them to the same name, unless imported functions conflict, or two of them are imported as a whole, without import list.

4) If possible, try to avoid using functions with the same name from different modules, even if these modules are renamed differently. If someone knows what function X.foo does, he is likely to be confused by the function Y.foo. If it's inevitable, consider creating a separate, very small, module that imports both functions and exports them under different names.