REVISED: Wednesday, February 7, 2024
Haskell Typing.
I. HASKELL TYPING
f :: a -> b
Types always start with a capital letter.
Prelude> :type True
True :: Bool
Prelude>
In Haskell, we denote a function f that takes as input a type a and gives as output a type b, as follows:
Every well formed expression has a type which can be automatically calculated at compile time using a process called type inference.
Types always start with a capital letter.
Prelude> :type True
True :: Bool
Prelude>
Haskell supports polymorphism for both data types and functions. The word “polymorphic” means “having many forms”; something which is polymorphic works for multiple types. Because the values in a list can have any type, we call the list type polymorphic. When we want to write a parametric polymorphic type, we use a type variable, which must begin with a lowercase letter. A type variable is a placeholder, where eventually we will substitute a real type.
A collection of related values is a type. A type system gives us abstraction. We can write the type “list of a” by enclosing the type variable in square brackets [a]. This amounts to saying “I don't care what type I have; I can make a list with it”.
In other languages you describe type by pointer-linked data structures; however, Haskell provides an implementation of data types similar to trees.
The combination of :: and the type after it is called a type signature; a declaration of the types of the function arguments and the function result.
The :: can be read "has type"; therefore, the function x has type Integer.
The -> function arrow, can be read as "to" or "returns" and can take two types; for example, Int and Bool and produce a new type Int -> Bool. The function arrow -> describes a function mapping from one type "to" another.
-> associates to the right, this means:
r :: Int -> (Int -> (Int -> (Int -> Int)))
The -> function arrow is used to bind a value to an object. Anything left of a -> function arrow is a parameter into your function. The thing to the right of the -> function arrow is the return value of your function. Haskell groups a chain of arrows from right to left; -> is right-associative; therefore, parentheses can be omitted.
TYPE MEANING
a -> a the type function from any type a to the same type a.
a -> a -> a the type function of two arguments of any type a to the same type a.
a -> Int the type function from any type a to Int.
Bool a Bool is a Boolean type. It can have only two values: True and False.
Char Char represents a character denoted by single quotes.
Double Double is a real floating point with double the precision.
Float Float is a real floating point with single precision.
Float Float is a real floating point with single precision.
Float -> Int the type function from Float to Int.
Int this type integer, has a minimum negative, and maximum positive value.
Int -> Int the type function from Int to Int.
Int -> Int -> Int the type function from an Int and returns something of the type Int -> Int; it returns a function that takes an Int and returns an Int.
Integer this type Integer has no size limit except the capacity of your computer.
Num a => a a being an instance of Num implies a.Int -> Int -> Int the type function from an Int and returns something of the type Int -> Int; it returns a function that takes an Int and returns an Int.
Integer this type Integer has no size limit except the capacity of your computer.
II. HASKELL TYPE CLASSES
A collection of types that support methods, overloaded operations, is called a type class.
A. BOUNDED
Bounded type class members have an upper and a lower bound.
B. ENUM
Enum type class members are sequentially ordered types; i.e., they can be enumerated.
C. EQ
Any type where it makes sense to test for equality between two values of that type is a member of the Eq, equality types, type class.
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
Maybe in itself is not a concrete type, it is a type constructor that takes one type parameter, like Char, to produce a concrete type, like Maybe Char; all the types in functions have to be concrete.
While Maybe is not a concrete type, Maybe m is. By specifying a type parameter, m, which is in lowercase, we said that we want all types that are in the form of Maybe m, where m is any type, to be an instance of Eq.
A Maybe value can either be Just something or Nothing. Much like a list can be either an empty list or a list with some elements, a Maybe value can be either no elements or a single element. And like the type of a list of integers is [Int], the type of maybe having an integer is Maybe Int.
Prelude> :module +Data.Maybe
Prelude> :module +GHC.Show
Prelude>
Prelude> :type maybe
maybe :: b -> (a -> b) -> Maybe a -> b
Prelude>
Prelude> :info maybe
maybe :: b -> (a -> b) -> Maybe a -> b -- Defined in `Data.Maybe'
Prelude>
The a here is the type parameter. And, because there is a type parameter involved, we call Maybe a type constructor. Depending on what we want this data type to hold when it is not Nothing, this type constructor can end up producing a type of Maybe Int, Maybe String, etc. No value can have a type of just Maybe, because that is not a type in itself, it is a type constructor. In order for this to be a real type, that a value can be part of, it has to have all its type parameters filled up.
If you want to see what the instances of a typeclass are, just do :info YourTypeClass in GHCi. Typing :info Num will show which functions the typeclass defines and it will give you a list of the types in the typeclass. :info also works for types and type constructors. If you do :info Maybe, it will show you all the typeclasses that Maybe is an instance of. :info can also show you the type declaration of a function.
Prelude> :info 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
-- 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'
Prelude>
Prelude> :info Maybe
data Maybe a = Nothing | Just a -- Defined in `Data.Maybe'
instance Eq a => Eq (Maybe a) -- Defined in `Data.Maybe'
instance Monad Maybe -- Defined in `Data.Maybe'
instance Functor Maybe -- Defined in `Data.Maybe'
instance Ord a => Ord (Maybe a) -- Defined in `Data.Maybe'
instance Read a => Read (Maybe a) -- Defined in `GHC.Read'
instance Show a => Show (Maybe a) -- Defined in `GHC.Show'
Prelude>
Maybe represents an option of either having nothing or having one of something. It does not matter what the type of that something is.
D. FROMINTEGRAL
fromIntegral takes an integral number and turns it into a more general number.
Prelude> :type fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b
Prelude>
For all types a and b,
if a is an instance of Integral and b is an instance of Num,
then fromIntegral can take a type a and produce a type b.
E. FRACTIONAL
Fractional, fractional types, are instances of Num which also support methods of fractional division and fractional reciprocation.
F. FUNCTOR
The Functor typeclass, is for things that can be mapped over. Mapping over lists is a dominant idiom in Haskell. The list type is part of the Functor typeclass.
class Functor f where
fmap :: (a -> b) -> f a -> f b
fmap has type function (a -> b), and is passed function f a and returns function f b.
f is not a concrete type, a type that a value can hold, like Int, Bool or Maybe String, but a type constructor that takes one type parameter.
fmap takes a function from one type to another and a functor applied with one type and returns a functor applied with another type.
For lists, fmap is just map.
map :: (a -> b) -> [a] -> [b]
Prelude> fmap (/3)[3,6,9]
[1.0,2.0,3.0]
it :: Fractional b => [b]Prelude> map (/3)[3,6,9]
[1.0,2.0,3.0]
it :: Fractional b => [b]
Prelude>
G. INTEGRAL
Integral, integral types, is a numeric class Num which also supports the methods of integer division and integer remainder. Integral describes types whose values are mathematical integers.
H. NUM
Num, numeric types, is a type class. Num contains only types which behave like numbers.
Num a :: a -> a -> a
The above example says, let a be a type belonging to the Num type-class. This is a function from type a to (a -> a). As previously mentioned, the -> function arrow is right associative; therefore, parentheses are normally omitted in the function signature.
Notice when there is more than one type, the types are separated by ->. The last type in the list is the type of the result of the function.
Adding a constraint to a type definition is essentially never a good idea. It has the effect of forcing you to add type constraints to every function that will operate on values of that type.
I. ORD
Ord, ordered types, is a type class for types that have an ordering.
J. READ
Read, readable types, is the opposite type class of Show.
In Haskell no function has two arguments. Instead all Haskell functions have only one argument. Taking two arguments is equivalent to taking one argument and returning a function taking the second argument as a parameter.
K. SHOW
Members of the Show, showable types, type class can be presented as strings.
III. HASKELL USER DEFINED TYPE CLASSES
Type classes define a set of functions that can have different implementations depending on the type of data they are given. A type a -> b in Haskell is actually implicitly understood to mean in mathematical notation (∀ a,b : a → b). For those of you who have been away from math for awhile, ∀ in mathematical notation means "for every"; : in mathematical notation means "such that"; and → in mathematical notation means "maps the set a into the set b". Therefore, (∀ a,b : a → b) can me read "For every a,b such that a function maps the set a into the set b".
∈ in mathematical notation means "is an element of".
Constructions like “∀ x : x ∈ S: T(x) → U(x)” are not really primitives in logic, they are shorthand for a logical implication: ∀ x : (x ∈ S) → (T(x) → U(x)). That is exactly what Haskell does with type classes, it writes them as a predicate; e.g., Num(x) with an implication symbol =>. The reason for the separate implication symbol is just to visually distinguish the part of the type that is a constraint predicate on the type variables. So when you write something like (Eq a) => a -> a -> Bool, what you are really saying is for all types a such that a is in Eq, this function has type a -> a -> Bool.
If you are not defining a type class for something very specific to your application, there is a good chance that there is already a suitable type class in the libraries, and you should use the library one, rather than defining your own. Your code will be much more portable and reusable if you take advantage of the type classes in the standard libraries.
Whether you are only going to use library classes, or define your own, you will still need to be able to read type class declarations, and read and write instances.
The keyword to define a type class in Haskell is class. A type class declaration consists of two basic parts. The first is a declaration of what operations, methods, an instance of the class must support. The second is a set of default implementations of those methods.
Class methods are treated as top level declarations in Haskell. They share the same namespace as ordinary variables; a name cannot be used to denote both a class method and a variable or methods in different classes.
IV. TYPE ANNOTATION
Type annotations are a way of explicitly saying what the type of an expression should be. We do that by adding :: at the end of the expression and then specifying a type.
Prelude> :type read
read :: Read a => String -> a
Prelude>
Can be read "For all types a, so long as a is an instance of Read, the function read takes one parameter of type String and returns an a". Read a is not a type expression, but rather it expresses a constraint on a type, and is called a context. Contexts are placed at the front of type expressions.
12
it :: Int
Prelude>
Prelude> read "12" :: Float
12.0Prelude> read "12" :: Float
it :: Float
Prelude>
Prelude> (read "12" :: Float) ** 12
8.9161e12
it :: Float
Prelude>
Prelude> (read "12" :: Float) ** 2
144.0
it :: Float
Prelude>
V. TYPING USER DEFINED FUNCTIONS
Defining the type of a function before its declaration is not mandatory. Haskell's type system is powerful enough to allow us to avoid writing any type signatures at all. However, type signatures are considered good practice, because type signatures are a very effective form of documentation; and help bring programming errors to light.
If you want to give your function a type signature declaration and you are unsure as to what the type signature should be, you can always write the function without a type signature and then check the function with :t.
The GHCi command :set +t gives us type information for every expression we enter. We can turn off +t at any time, using the GHCi command :unset +t.
VI. GHCI TYPE DECLARATIONS
[] is pronounced "list of" when it is not an "empty list". For example [a] is read "list of" a.
head :: [a] -> a -- gets the first element of a list
tail :: [a] -> [a] -- gets everything but the first element
last :: [a] -> a -- gets the last element of a list
init :: [a] -> [a] -- gets everything but the last element
(++) :: [a] -> [a] -> [a] -- concatenates two lists together
(:) :: a -> [a] -> [a] -- prepends an element to a list
fst :: (a,b) -> a -- gets the first element of a tuple
snd :: (a,b) -> b -- gets the second element of a tuple
As shown below, GHCi can create either type Int integers or type Integer integers.
Prelude> let x :: [Int]; x = [6,7,8]
x :: [Int]
Prelude>
Prelude> let x :: [Integer]; x = [6,7,8]
x :: [Integer]
Prelude>
VII. CONCLUSION
In this tutorial, you have received an introduction to Haskell typing.
VIII. REFERENCES
Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.
Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.
Goerzen, J. & O'Sullivan, B. & Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.
Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.
Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.
Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.