Is it bad form to make new types/datas for clarity?

I wouldn't consider that bad form, but clearly, I don't speak for the Haskell community at large. The language feature exists, as far as I can tell, for that particular purpose: to make the code easier to read.

One can find examples of the use of type aliases in various 'core' libraries. For example, the Read class defines this method:

readList :: ReadS [a]

The ReadS type is just a type alias

type ReadS a = String -> [(a, String)]

Another example is the Forest type in Data.Tree:

type Forest a = [Tree a]

As Shersh points out, you can also wrap new types in newtype declarations. That's often useful if you need to somehow constrain the original type in some way (e.g. with smart constructors) or if you want to add functionality to a type without creating orphan instances (a typical example is to define QuickCheck Arbitrary instances to types that don't otherwise come with such an instance).


You're using type aliases, they only slightly help with code readability. However, it's better to use newtype instead of type for better type-safety. Like this:

data Alignment = LeftAl | CenterAl | RightAl
newtype Delimiter = Delimiter { unDelimiter :: Char }
newtype Width     = Width { unWidth :: Int }

setW :: Width -> Alignment -> Delimiter -> String -> String

You will deal with extra wrapping and unwrapping of newtype. But the code will be more robust against further refactorings. This style guide suggests to use type only for specializing polymorphic types.


Using newtype—which creates a new type with the same representation as the underlying type but not substitutable with it— is considered good form. It's a cheap way to avoid primitive obsession, and it's especially useful for Haskell because in Haskell the names of function arguments are not visible in the signature.

Newtypes can also be a place on which to hang useful typeclass instances.

Given that newtypes are ubiquitous in Haskell, over time the language has gained some tools and idioms to manipulate them:

  • Coercible A "magical" typeclass that simplifies conversions between newtypes and their underlying types, when the newtype constructor is in scope. Often useful to avoid boilerplate in function implementations.

    ghci> coerce (Sum (5::Int)) :: Int

    ghci> coerce [Sum (5::Int)] :: [Int]

    ghci> coerce ((+) :: Int -> Int -> Int) :: Identity Int -> Identity Int -> Identity Int

  • ala. An idiom (implemented in various packages) that simplifies the selection of a newtype that we might want to use with functions like foldMap.

    ala Sum foldMap [1,2,3,4 :: Int] :: Int

  • GeneralizedNewtypeDeriving. An extension for auto-deriving instances for your newtype based on instances available in the underlying type.

  • DerivingVia A more general extension, for auto-deriving instances for your newtype based on instances available in some other newtype with the same underlying type.