tl; dr () does not add a null value for each type, no damn thing; () is a "dumb" value in its own type: () .
Let me step back a little from the question and turn to a common source of confusion. The main thing to learn when learning Haskell is the difference between an expression language and its type. You probably know that these two people are separate. But this allows you to use the same symbol in both, and this is what happens here. There are simple text tips to tell you what language you are looking at. You do not need to analyze the entire language to detect these signals.
The top level of the Haskell module lives by default in the expression language. You define functions by writing equations between expressions. But when you see foo :: bar in the expression language, it means that foo is an expression and bar is its type. Therefore, when you read () :: () , you see an instruction that associates () in the expression language with () in the type language. Two characters () mean different things, because they are not in the same language. This repetition often causes confusion for beginners until the separation of the language of expression / type is established in their subconscious, and at that moment it becomes mnemonic.
The data keyword represents a new data type declaration that includes a thorough mix of expression languages and types, because it first tells you what the new type is, and secondly, what its meanings are.
data TyCon tyvar ... tyvar = ValCon1 type ... type | ... | ValConn type ... type
In such a declaration, the TyCon type constructor is added to the type language, and ValCon value constructors are added to the expression language (and its pattern sublanguage). In the data declaration, things in the argument places for ValCon s tell you the types specified for the arguments when this ValCon is used in expressions. For example,
data Tree a = Leaf | Node (Tree a) a (Tree a)
declares a Tree type constructor for binary tree types that store items in nodes whose values are specified by the Leaf and Node value constructors. I like to use the constructors of the color tree (Tree) blue and the constructors of the values (Leaf, Node) red. Expressions should not have blue ones, and (if you do not use advanced functions) there are no red types. You can declare a built-in type of Bool ,
data Bool = True | False
adding blue Bool to the type of language, and red True and False to the language of the expression. Unfortunately, my markdown-fu is inadequate to the task of adding colors to this post, so you just need to learn how to add colors to the head.
The unit type uses () as a special character, but it works as if declared
data () = () -- the left () is blue; the right () is red
means that the synonym () is a type constructor in the type language, but conceptually red () is a value constructor in the expression language and really () :: () . [This is not the only example of such a pun. Types of large tuples follow the same pattern: the pair syntax is as if given
data (a, b) = (a, b)
adding (,) to type and expression languages. But I'm distracted.
Thus, type () , often pronounced “Unit”, is a type containing one value that is worth mentioning: this value is written () , but in the expression language it is sometimes pronounced “void”. A single value type is not very interesting. A value of type () introduces zero bits of information: you already know what it should be. So, while there is no special type () to indicate side effects, it is often displayed as a component of a value in a monadic type. Monadic operations usually have types that look like
val-in-type-1 -> ... -> val-in-type-n -> effect-monad val-out-type
where the return type is the type application: the function tells you what effects are possible, and the argument tells you what value is generated by the operation. for example
put :: s -> State s ()
which is read (because the application is associated with the left ["as we did in the sixties," Roger Hindley]) as
put :: s -> (State s) ()
has one type of input of the value s , the effect monad State s and type of output of the value () . When you see () as the type of output of the value, it means that "this operation is used only for its effect, while the value is uninteresting." Similarly
putStr :: String -> IO ()
passes a string to stdout but returns nothing interesting.
Type () also useful as an element type for container-like structures, where it indicates that the data consists only of a form without any interesting payload. For example, if Tree declared above, then Tree () is a type of binary tree-like forms without storing anything interesting in nodes. Similarly, [()] is a type of lists of dim elements, and if there is nothing interesting in the elements of a list, then the only information that it contributes is its length.
To summarize, () is a type. Its one value, () , has the same name, but this is normal, because type and expression languages are separate. It is useful to have a type representing "no information" because in a context (such as a monad or container) it tells you that only the context is interesting.