Skip to content

📘 Haskell Tutorial: Understanding Modules and Imports Haskell

Bernard Sibanda edited this page Oct 28, 2025 · 1 revision

📑 Table of Contents

  1. 🧩 Introduction to Haskell Modules
  2. 📦 How Imports Work
  3. 🌳 Standard Haskell Module Hierarchy
  4. 🎯 Qualified Imports Explained
  5. 🧠 Using Aliases with as
  6. ⚙️ Mixing Qualified and Unqualified Imports
  7. 🔧 Best Practices and Common Idioms
  8. 🧾 Glossary of Key Terms
  9. 🧭 Conclusion

1. 🧩 Introduction to Haskell Modules

In Haskell, modules are the primary way to organize code. A module is simply a collection of related definitions—functions, types, and values—that can be imported into other programs.

Example module:

-- File: Math/Geometry.hs
module Math.Geometry (areaCircle) where

areaCircle :: Floating a => a -> a
areaCircle r = pi * r^2

To use it elsewhere:

import Math.Geometry

main = print (areaCircle 5)

📘 Key Idea: Modules promote modularity, code reuse, and namespace isolation.

2. 📦 How Imports Work

By default, every Haskell file automatically imports:

import Prelude

Prelude provides the most common functions and types (map, foldr, Maybe, etc.).

But you can import other modules manually:

import Data.List
import System.IO

This lets you use everything those modules export.

✨ Example

import Data.List
main = print (sort [3,1,2])

Result:

[1,2,3]

If you want only specific functions:

import Data.List (sort, nub)

If you want to hide a function:

import Data.List hiding (nub)

3. 🌳 Standard Haskell Module Hierarchy

The base library (installed with GHC) provides all standard modules.

Category Example Modules Description
Core Prelude, Data.List, Data.Maybe Everyday functions & types
Control Control.Monad, Control.Applicative Functional control structures
System System.IO, System.Directory File, process, and I/O handling
Numeric Data.Ratio, Numeric Rational, complex, and numeric utilities
Collections Data.Map, Data.Set, Data.Sequence Efficient data structures
Text Data.Text, Data.ByteString String and binary data
Debugging Debug.Trace Debug output
GHC GHC.Base, GHC.Exts Low-level internals

Example:

import Data.List (sort, nub)
import Control.Monad (forM_)
import System.IO (readFile)

4. 🎯 Qualified Imports Explained

🔹 What Is a Qualified Import?

When you import a module qualified, its functions and types must be prefixed with the module name.

This prevents naming conflicts between modules that export functions with the same name.

import qualified Data.List

main = print (Data.List.nub [1,1,2,3])

🔹 Why Use qualified?

✅ Prevents name clashes ✅ Makes origins of functions explicit ✅ Improves readability in large projects ✅ Supports modular namespace design

⚠️ Without qualified

import Data.List
import Data.Set

main = print (map (*2) (fromList [1,2,3]))

❌ Error:

Ambiguous occurrence ‘map’
It could refer to either ‘Data.List.map’ or ‘Data.Set.map’

✅ With qualified

import qualified Data.List
import qualified Data.Set

main = do
  print (Data.List.map (*2) [1,2,3])
  print (Data.Set.map (*2) (Data.Set.fromList [1,2,3]))

Now Haskell knows exactly which map you mean.

5. 🧠 Using Aliases with as

You can create shorter aliases using as — a very common idiom in Haskell.

import qualified Data.List as L
import qualified Data.Set  as S

Then:

main = do
  print (L.nub [1,1,2,3])
  print (S.member 2 (S.fromList [1,2,3]))

💡 Common alias conventions:

Module Alias Example
Data.List L L.sort [3,1,2]
Data.Map M M.lookup "key" m
Data.Set S S.fromList [1,2,3]
Data.Text T T.pack "hello"
Data.ByteString B B.pack [72,101,108,108,111]

6. ⚙️ Mixing Qualified and Unqualified Imports

You can use both styles in the same file for balance between brevity and clarity.

import Data.List (sort)
import qualified Data.List as L

main = do
  print (sort [3,1,2])     -- unqualified
  print (L.nub [1,1,2,3])  -- qualified

Guideline: Use unqualified imports for commonly used, unambiguous functions (like sort), and qualified ones for context-specific or conflicting names (nub, map, etc.).

7. 🔧 Best Practices and Common Idioms

🪄 Always Qualify Collections

import qualified Data.Map as M
import qualified Data.Set as S

Helps differentiate between M.map and S.map.

🧩 Use Text Instead of String

import qualified Data.Text as T
import qualified Data.Text.IO as TIO

main = TIO.putStrLn (T.pack "Hello, Haskell!")

⚙️ Avoid Wildcard Imports in Libraries

Prefer explicit imports:

import Data.List (sort, nub)

✅ Keeps code maintainable and avoids pollution of the global namespace.

🧱 When Defining DSLs

You can hide Prelude entirely:

{-# LANGUAGE NoImplicitPrelude #-}
import qualified Prelude as P

main = P.print (P.map (+1) [1,2,3])

Used in advanced libraries (e.g., embedded DSLs, smart contract DSLs).

8. 🧾 Glossary of Key Terms

Term Meaning
Module A file or namespace containing related definitions.
Import Brings names from another module into scope.
Qualified Import Requires names to be prefixed by module or alias.
Alias (as) A shorthand prefix for a qualified module.
Prelude The automatically imported base module with common functions.
Namespace Logical grouping of identifiers to prevent naming clashes.
Export List Controls what a module exposes (module M (f, g, h) where).
NoImplicitPrelude Compiler flag disabling automatic Prelude import.

9. 🧭 Conclusion

Haskell modules are a powerful feature for building clean, modular, and reusable programs. ✅ Qualified imports and aliases make large projects manageable by keeping namespaces clear. ✅ Use explicit imports to prevent ambiguity and improve readability.

Once you master imports, your Haskell codebase becomes organized, conflict-free, and easy to navigate — no matter how large it grows.

Clone this wiki locally