Skip to content

More Comprehensive Information

Most of the time, it's enough to know the type of a value. Sometimes, we may also want to know about where it is defined and, in case of an operator, its "fixity" (precedence level and left or right-associativity). To query this information, we can use :info or :i:

GHCi
>>> :i (+)
class Num a where
  (+) :: a -> a -> a
  ...
    -- Defined in ‘GHC.Num’
infixl 6 +

This tells us that the addition operator is a method of the Num type class. In particular, if we use addition in any of our functions, then the arguments to which we want to apply this operator must be of a type that is an instance of this type class, that implements addition and many other arithmetic operators. It also tells us that, if a is an instance of Num, then (+) takes two arguments of type a and returns a result of type a. The precedence level of (+) is 6, which is lower then the precedence level of (*), so we can write arithmetic expressions as we are used to, without parentheses:

GHCi
>>> :i (*)
class Num a where
  ...
  (*) :: a -> a -> a
  ...
    -- Defined in ‘GHC.Num’
infixl 7 *

Both (+) and (*) are left-associative (infixl). This means that the expression 2 + 3 + 4 is evaluated as if it were parenthesized as (2 + 3) + 4. The other option is that an operator is right-associative (infixr). If addition were right-associative, the expression would be parenthesized as 2 + (3 + 4). For addition, the difference is not important, but for subtraction, left-associativity is important because we want 3 - 2 - 1 to mean (3 - 2) - 1 = 0, not 3 - (2 - 1) = 2.

Finally, the definition of the Num type class, and of (+) and (*) along with it, is provided in the GHC.Num module, which is part of the standard library. What else is defined in the Num type class?

GHCi
>>> :i Num
class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
    -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

Ah, okay, so every type a that is an instance of Num must implement addition, subtraction, multiplication, negation, absolute value, sign, and a conversion from Integer to type a. Let's ignore the MINIMAL line for now. The last five lines are interesting because they tell us that Word, Integer, Int, Float, and Double are instances of Num defined already in the standard library—they are number types, so all of the operations defined in the Num type class are available for arguments of theses types.

Getting information about type definitions can also be useful if we want to write functions that take values of these types as arguments. The function needs to be able to deal with every possible value of that type, so if we use pattern matching to do different things based on different values, we need to know which values there are. Here, for example, is the definition of the Bool type:

GHCi
>>> :i Bool
data Bool = False | True    -- Defined in ‘GHC.Types’
instance Eq Bool -- Defined in ‘GHC.Classes’
instance Ord Bool -- Defined in ‘GHC.Classes’
instance Show Bool -- Defined in ‘GHC.Show’
instance Read Bool -- Defined in ‘GHC.Read’
instance Enum Bool -- Defined in ‘GHC.Enum’
instance Bounded Bool -- Defined in ‘GHC.Enum’

This says that there are two values of type Bool: False and True. We'll learn about type definitions using the data keyword later. It also says that Bool is an instance of

  • Eq: Two Boolean values can be tested for equality.

    GHCi
    >>> False == False
    True
    >>> False == True
    False
    
  • Ord: Boolean values can be ordered, with False < True.

    GHCi
    >>> False < True
    True
    >>> True < False
    False
    
  • Show: Boolean values can be printed in human-readable form.

    GHCi
    >>> show False
    "False"
    >>> show True
    "True"
    
  • Read: Boolean values can be constructed from the string values "False" and "True".

    GHCi
    >>> read "False" :: Bool
    False
    >>> read "True" :: Bool
    True    
    
  • Enum: Bool is an enumeration type. Haskell knows how to enumerate all Boolean values in a given range of Boolean values. This is pretty trivial in this case because there are only two such values.

    GHCi
    >>> [False .. True]
    [False,True]
    
  • Bounded: Bool is a bounded type, a type that has a minimum value and a maximum value.

    GHCi
    >>> minBound :: Bool
    False
    >>> maxBound :: Bool
    True
    

I mentioned before that in Haskell, strings are simply lists of characters, and :info confirms this:

GHCi
>>> :i String
type String = [Char]    -- Defined in ‘GHC.Base’

The difference here is that String is defined using type, not data, which makes it a type alias, a type that is completely interchangeable with [Char], the type of lists of characters.