Namespaces, modules, qualified imports and a constant pain

February 27, 2015
Yuras Shumovich

This post is highly opinionated. It is about Haskell not having good namespace story. I’ll try to describe my point of view to this issue.

T? M? BS?

What do this letters mean for you? Most likely they are Data.Text, Data.Map and Data.ByteString. It is common to import this modules qualified and introduce short aliases:

import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T

foo = BS.length . BSL.toStrict . BSL.fromChunks . map T.encodeUtf8

It probably works for well known modules, but too often you can see LM, I, LT, ST and so on. That becomes a tradition to use short names. But is it a good tradition?

Short names make perfect sense in polymorphic code:

catMaybes :: [Maybe a] -> [a]
catMaybes ls = [x | Just x <- ls]

Here a and x can mean anything, so descriptive names will be misleading.

But it is not the case for module names. Short names here are confusing because there is no common scheme, T can be used for Data.Text of Data.Traversable. Is it so hard to type few letters?

import qualified Data.ByteString as ByteString
-- Yes, you can have a dot in module name alias:
import qualified Data.ByteString.Lazy as Lazy.ByteString
import qualified Data.Text as Text

foo = ByteString.length
    . Lazy.ByteString.toStrict
    . Lazy.ByteString.fromChunks
    . map Text.encodeUtf8

It is definitely longer and a bit noisy, but at least it is unambiguous.

But it becomes a real pain, regardless of short vs long alias, when we have long declaration (or a set of declarations) that use a lot of functions from the same qualified module:

import qualified Data.ByteString as ByteString

foo = ByteString.length
    . ByteString.append "!"
    . ByteString.drop 5
    . ByteString.take 10
    . ByteString.pack

Probably the best thing to do here is to refactor the declaration out to a separate module and import Data.ByteString unqualified. But it would be cool to be able to open particular module locally:

import qualified Data.ByteString as ByteString

foo = length
    . append "!"
    . drop 5
    . take 10
    . pack
  where
  ByteString{..} = import ByteString

somethingName, somethingEmail, somethingAge

Another common tradition is to prefix each record name or function with a prefix to be able to use them unqualified:

data Something = Something
  { somethingName :: Text
  , somethingEmail :: Email
  , somethingAge :: Int
  }

foo :: Something -> Text
foo something = "name:" <> somethingName something

Such a prefix is a poor mans namespace. Unfortunately in Haskell to create named namespace, we have to create a module:

module Something where

data Something = Something
  { name :: Text
  , email :: Email
  , age :: Int
  }

module Foo where

import Something (Something)
import qualified Something

foo :: Something -> Text
foo something = "name:" <> Something.name something

It is definitely better IMO. But it would be cool if datatype declaration will introduce a namespace, so that we don’t have to create a module:

data Something = Something
  { qualified name :: Text
  , qualified email :: Email
  , qualified age :: Int
  }

foo :: Something -> Text
foo something = "name:" <> Something.name something

The syntax is terrible, I know. Probably there should be better syntax, ideally we should be able to declare free functions withing the data type namespace:

data Person = Person
  { firstName :: Text
  , lastName :: Text
  }

  -- Note identation
  fullName :: Person -> Text
  fullName person = firstName person <> " " <> lastName person

foo person = "full name:" <> Person.fullName person

See also nested modules

Type-directed name resolution, overloaded record fields, etc

I believe that code should be as unambiguous as it is possible. Adhoc polymorphism makes code ambiguous – you can’t anymore say what function is called only looking to it’s usage side. Some recently proposed extensions has their own value (e.g. I like anonymous records), but I believe that namespaces support in Haskell should be improved, because namespaces is the right solution for the described issues.

More posts

Atom feed

Atom feed