1. The Easy Part
  2. Higher Order Functions
  3. Types
  4. Lists
  5. Complex Data Objects
  6. File IO
  7. Classes
  8. Monads
  9. List Monads
  10. Shared Transactional Memory


Not Weakly Typed

The first lesson may have led you to believe Haskell is weakly typed. After all, there were no type declarations anywhere. In fact, Haskell is implicitly typed, inferring types based on usage and then strongly enforcing.

1foo n = 2*n+1
2main = putStrLn("out = "++foo(3))
$ ghc --make wrong.hs
[1 of 1] Compiling Main             ( wrong.hs, wrong.o )

    No instance for (Num [Char])
      arising from the literal `3' at wrong.hs:2:30
    Possible fix: add an instance declaration for (Num [Char])
    In the first argument of `foo', namely `(3)'
    In the second argument of `(++)', namely `foo (3)'
    In the first argument of `putStrLn', namely `("out = " ++ foo (3))'

The problem is that foo is inferred to be of type Num (i.e. numeric), but a [Char] is needed by operator++. We can easily fix this by replacing foo(3) with show(foo(3)).

A Type Problem

Here is a less intuitive problem. This code works...

1main =
2    let
3        d = 3
4        m = mod d 2
5    in
6        putStrLn("m="++show(m))
$ ghc --make int.hs
[1 of 1] Compiling Main             ( int.hs, int.o )
Linking int ...
$ ./int

... and so does this ...

1main =
2    let
3        d = 3
4        s = sqrt d
5    in
6        putStrLn("s="++show(s))
$ ghc --make doub.hs
[1 of 1] Compiling Main             ( doub.hs, doub.o )
Linking doub ...
$ ./doub

... but this fails.

1main =
2    let
3        d = 3
4        m = mod d 2
5        s = sqrt d
6    in
7        do
8            putStrLn("m="++show(m))
9            putStrLn("s="++show(s))
$ ghc --make mix.hs
[1 of 1] Compiling Main             ( mix.hs, mix.o )

    Ambiguous type variable `t' in the constraints:
      `Floating t' arising from a use of `sqrt' at mix.hs:5:12-17
      `Integral t' arising from a use of `mod' at mix.hs:4:12-18
    Probable fix: add a type signature that fixes these type variable(s)

What happened here? Haskell treats the numeric constant "3" as being of type Num. This is a type which Haskell is able to convert either to an Integral (i.e. an integer of some sort) or Floating (i.e. a real of some sort). In int.hs, the "m = mod d 2" causes Haskell to infer the type of "d" to be Integral. In doub.hs, "s = sqrt d" causes Haskell to infer the type of "d" to be Floating. If both are present, Haskell does not know what to do.

1main =
2    let
3        d = 3
4        m = mod d 2
5        s = sqrt (fromIntegral d)
6    in
7        do
8            putStrLn("m="++show(m))
9            putStrLn("s="++show(s))
$ ghc --make mix2.hs
[1 of 1] Compiling Main             ( mix2.hs, mix2.o )
Linking mix2 ...
$ ./mix2

Here the function fromIntegral() converts an Integral back to a Num. This value can now be interpreted as a Floating when it is used by sqrt(). The symbol d can now safely be inferred to be of type Integral, and Haskell is happy.

Declaring Function Types

It is also possible to declare the type of a function, avoiding Haskell's implicit typing system.

1foo:: Int -> Int
2foo n = 2*n+1
3main = putStrLn("out = "++show(foo(3)))
$ ghc --make right.hs
[1 of 1] Compiling Main             ( right.hs, right.o )
Linking right ...
$ ./right
out = 7

The line "foo:: Int -> Int" means the function foo takes an Int and returns an Int. What happened to Num and Integral?

The types Num, Integral, and Floating in Haskell are not really concrete types, but something called classes. These are similar to Java's interfaces and just describe the set of operators and functions that apply to one of several concrete types.

The standard concrete types matching the Integral class are Int (a fixed precision integer) and Integer (an arbitrary precision integer).

By making the declaration above, we've chosen to have the function apply only to fixed precision integers. Had we left the definition off, Haskell would have used a template definition that would allow any concrete type matching the Integral class to work.

1foo:: (Num a) => a -> a
2foo n = 2*n+1
3main = putStrLn("out = "++show(foo(3)))
$ ghc --make right2.hs
[1 of 1] Compiling Main             ( right2.hs, right2.o )
Linking right2 ...
$ ./right2
out = 7

Above we see an example of the template definition based on Num. The lower case name "a" is the template parameter.

1pow1 :: Int -> Int -> Int
2pow1 a b = a ^ b
4pow2 :: Integer -> Integer -> Integer
5pow2 a b = a ^ b
7main =
8    do
9        putStrLn("2^10="++show(pow1 2 10))
10        putStrLn("2^10="++show(pow2 2 10))
11        putStrLn("2^100="++show(pow1 2 100))
12        putStrLn("2^100="++show(pow2 2 100))
$ ghc --make ivsi.hs
[1 of 1] Compiling Main             ( ivsi.hs, ivsi.o )
Linking ivsi ...
$ ./ivsi

The line "pow1:: Int->Int->Int" means that pow1 takes an Int and returns a function that takes an Int and returns an Int.

This example compares a fixed to a finite precision version of the pow function.

One way to learn about the types of functions is to use Hugs, the Haskell interpreter. Just type "ghci yourfile.hs" and when Hugs starts you can type ":t myfunc" and Hugs will show you the type declaration for your function as inferred by Haskell.

To help you find your way throught the many types and declarations of the Haskell world, please use Hoogle (http://www.haskell.org/hoogle/).