tutorials-haskellTutorial

Haskell tutorial

by Bayle Shanks

some code samples are from __Yet Another Haskell Tutorial__ which is by Hal Daume III

I'm writing this after I read some tutorials myself but before actually programming anything in Haskell -- it's a "blind leading the blind" situation. So this tutorial is probably filled with errors and misunderstandings. After I get some experience with Haskell I might come back and check it over and then remove this warning, if I ever get around to it

Preface

This (currently unfinished) tutorial aims to be short yet to cover topics through basic monads.

Much of the bulk of the text consists of short code examples and interpreter traces.

The intended reader has programming experience, but only in imperative languages, and wants a quick introductory tour of Haskell. The tutorial is not intended to be difficult, however in the interest of brevity many things are demonstrated without much explanation.

It does not aim to be comprehensive, so after reading it, interested parties may want to read a longer tutorial to get the details that are skipped here. Hopefully, after reading this tutorial, you will be able to breeze through a longer one.

Copyright

Copyright 2008 Bayle Shanks. You may copy this document under the terms of either:

whichever you prefer (but not in any other circumstances without prior permission).

Note to people reading this on the wiki

(ignore this if you are reading the PDF). I'm using EasyLatex? to compile the wiki source of this page into a PDF. So don't mind the occasional LaTeX? commands. For example:

\usepackage{times} \usepackage[margin=3.7cm]{geometry}

\newpage

Hello World


As of this writing, the most popular implementation of Haskell seems to be ghc, the Glasgow Haskell Compiler. The GHC project also produces ghci, an interpreter. In this tutorial, we'll use ghci (although of course most of the things that we'll talk about are general features of the language that will be in any implementation). So go install ghci now.

When you start ghci, you get an intro banner and then a prompt:

Prelude> 

To quit, type Cntl-D.

Start ghci again. You can enter Haskell directly in the interpreter, for example:

Prelude> print "Hello World"
"Hello World"
Prelude> 

If you want to put this in a file, here's how. Each file is a module. The module name should be the same as the file name, except that the file name should have an .hs at the end and the module name should not.

So, create a file called Test.hs and put this into it:

module Test where
helloWorldFunction = print "Hello world"

To load a module in ghci, you say ":l modulename". So, to load module Test and then evaluate helloWorldFunction, you do:

Prelude> :l Test
[1 of 1] Compiling Test             ( Test.hs, interpreted )
Ok, modules loaded: Test.
*Test> helloWorldFunction
"Hello world"
*Test> 

Note that prompt changes from "Prelude" to "*Test". If you later change the sourcecode file and want to reload it, you can use :r, which reloads the current module.

In the previous example, the reason that there are quotes around "Hello world" is that the "print" command prints a representation of a value that is useful for debugging. If you just have a string that you want to send to the console directly, you use putStrLn. For example:

Prelude> print "Hello world"
"Hello world"
Prelude> putStrLn "Hello world"
Hello world
Prelude> 

If you want to compile a standalone executable, you need to make a Main module. Put the following into Main.hs:

module Main where
main = putStrLn "Hello world"

Now, at your operating system's command line, use ghc to compile:

$ ghc --make Main.hs -o main
$ ./main
Hello world

\newpage

Basic syntax


Haskell is **case-sensitive**

In addition,

  1. The names of types, typeclasses, and modules must begin with an upper-case letter.
  2. The names of infix functions must be composed of punctuation symbols.
  3. The names of almost1 everything else must begin with a lower-case letter.

Types

In Haskell, every expression has a type2. Various functions and operators have various restrictions on what sort of types they can operate on. If you try to use incompatible types, you'll get a compile time error.

If you are curious about the type of some expression in Haskell, you can use ghci's :t command to find out. For example:

Prelude> :t 'c'
'c' :: Char
Prelude> :t "a string"
"a string" :: [Char]

Later on we'll talk about how to read the notation for expressing the types of things in Haskell. Until then, don't worry about it.

At this point you might be worried that you'll spend a lot of time declaring the types of things, so I should mention that Haskell doesn't usually require you to declare the types of your variables and functions. Haskell has a powerful system of type inference that guesses the types of almost everything automatically.

A disadvantage of using a powerful type inference system is that it makes type error messages harder to interpret. For example, let's say that you have three expressions, call them A,B,C. Let's say that you give expression A the wrong type. Let's say that you construct expression B out of expression A, and then in a very different part of the program, you refer to expression B in expression C. Because you made a mistake with expression A, Haskell might infer the wrong type for expressions B and C. Perhaps the error will only surface when it gets to expression C. In this case, the error message will refer only to expression C, and you'll have to figure out that the root of the problem was really with expression A.

Defining functions

To define a function in a source code file, write something like:

functionName argument1 argument2 = expression

For example:

plusFive a = a+5

Inside ghci, you have to put the word "let" at the beginning of the function definition:

Prelude> let plusFive a = a+5
Prelude> plusFive(10)
15

Functions you define are prefix by default. We'll talk about how to define infix functions later.

Calling functions

To call a prefix function, just write the function, and then a space, and then the argument. If there are multiple arguments, just separate them by spaces. For example:

Prelude> let addition a b = a+b
Prelude> addition 2 3
5

__do__ blocks

In Haskell, if you want to execute a sequence of instructions, you can't just put them one after another, unless they are within a do. For example, try putting this incorrect code into Test.hs:

module Test where

test = print "First line."
       print "Second line."

It won't compile in ghci (don't worry about trying to understand this error message3):

Prelude> :l Test
[1 of 1] Compiling Test             ( Test.hs, interpreted )

Test.hs:2:7:
    Couldn't match expected type `(a -> IO ()) -> [Char] -> t'
	   against inferred type `IO ()'
    In the expression: print "First line." print "Second line."
    In the definition of `test':
	test = print "First line." print "Second line."
Failed, modules loaded: none.

The problem is that in Haskell, a function is just __a single expression__. So in a sense, a whole Haskell function is analogous to just a single line of code in other languages.

To execute a sequence of instructions, you have to wrap them in a do block. Example in ghci:

Prelude> do {putStrLn "First line."; putStrLn "Second line."}
First line.
Second line.

Example as a source code file (to be placed in Test.hs):

module Test where
test = do {putStrLn "First line."; putStrLn "Second line."}

A complete do block is itself a single expression that returns a single value (which is why you can have a whole do block inside a function, even though functions must be single expressions).

Under the hood, do blocks are actually syntactic sugar for something pretty complicated involving monads. We'll talk more about the deeper meaning of do blocks later. For now, one more thing to note is that a do block that contains I/O has a return value of type 'IO something' (where the 'something' varies).

Lack of destructive variable updates

You don't have mutable variables in Haskell.

Put the following incorrect code into Test.hs:

module Test where
x = 1
x = x+1

Now try loading it in ghci:

Prelude> :l Test
[1 of 1] Compiling Test             ( Test.hs, interpreted )

Test.hs:3:0:
    Multiple declarations of `Test.x'
    Declared at: Test.hs:2:0
		 Test.hs:3:0
Failed, modules loaded: none.

To accomplish the same effect, you have to have a different name for the so-called variable at each stage. Put this into Test.hs:

module Test where
x = 1
x2 = x+1

Now it works:

Prelude> :l Test
[1 of 1] Compiling Test             ( Test.hs, interpreted )
Ok, modules loaded: Test.
*Test> x2
2

You can see that what you are really doing here is not declaring "variables", but defining functions, just like the "addition" function in the example above. There are still variables in Haskell, in the form of function arguments; but these are just like the variables found in mathematics -- their value doesn't change over the course of the computation.

However, for me, the word "variable" in the context of a programming language is so bound up the idea of mutable variables that I like to tell myself this: in Haskell, there are no "variables", because nothing "varies"; there are just functions, each with a fixed4 definition.

I'll say that again. In the above code, x and x2 do not correspond to memory locations which you can read and write. x and x2 are names of functions that you have defined.

How can this be?

Now, you're thinking, how are we going to program without mutable variables? Well, as far as I know (todo), there are 3 ways in which variables are used in typical imperative programming languages:

  1. Cases in which the same variable is assigned different values on different lines of code.
  2. Function arguments.
  3. Things that change as you progress through loop iterations.

I'll treat each case in turn.

In the first case, these variables don't really have to be mutable -- you could always5 give the variable a new name on each line of code that assigns to it6.

An example of the first case, in Python:

import time

toOutput = "The time is "
toOutput = toOutput + time.strftime('%H:%M')
toOutput = toOutput + " right now"
print toOutput

In this situation, toOutput can be replaced by three different variables:

import time

toOutput1 = "The time is "
toOutput2 = toOutput + time.strftime('%H:%M')
toOutput3 = toOutput + " right now"
print toOutput3

The second case is function arguments. Function arguments aren't usually considered "mutable variables", but they do take on different values each time the function is called. Well, we do have function arguments in Haskell, so there's no problem here.

The third case are things that change as you progress through loop iterations. Well, that's easily taken care of. In Haskell, you're not allowed to loop. Feel better?

That's a half-truth. In Haskell, you replace loop constructs with recursion. Each iteration is replaced with a new call to the function. So, you can still go through the same section of code over and over again while incrementing the equivalent of a loop counter -- it's just that the loop counter is a function argument, rather than a local variable, and that each iteration is a separate function call. I'll give an example later.

By the way, you can put some nonalphanumeric characters into your function names. Some people like to use the apostrophe (for example, "x'" -- read "x prime") to indicate to the reader that a bunch of functions should be considered to form sequence. Example:

module Test where
x = 1
x' = x+1

You can redefine functions within __do__ blocks

To reassign a name within a do block, use something like "let {x=1}". Example:

Prelude> do {let {s="Line 1"}; putStrLn s; let {s="Line 2"}; putStrLn s;}
Line 1
Line 2

However, this doesn't mean that we have mutable variables. The following code enters an infinite loop when Haskell tries to evaluate "x=x+1":

Prelude> do {let {x=1}; print x; let {x=x+1}; print x;}

You can redefine functions within ghci

You can also reassign things within the ghci interpreter using let. You might think of the things that you enter in the interpreter as being within an implicit do block7. Example:

Prelude> let x = 3
Prelude> x
3
Prelude> let x = 4
Prelude> x
4
Prelude> let f a b = a+b
Prelude> f 2 3
5
Prelude> let f a b = a*b
Prelude> f 2 3
6

Using whitespace as shorthand for \{\} and ;

You can either enclose blocks in curly brackets and delimit lines with semicolons, or you can use whitespace. If you use whitespace, just indent things that you would have put into curly brackets.

In the ghci interpreter, you cannot use whitespace, you must use only "{}" and ";". You can only use whitespace in source code files.

Here is an example using "{}" and ";":

module Test where
test = do {putStrLn "First line."; putStrLn "Second line."}

And here is the equivalent using whitespace:

module Test where
test = do 
  putStrLn "First line."
  putStrLn "Second line."

The rules for interpretation of whitespace in Haskell are sometimes called the rules of "layout".

Generally for the rest of this tutorial when I present code samples, I'll use the whitespace form, as if it was sitting inside a source code file. Sometimes, however, when I want to show you what happens when you evaluate something, I'll show you a ghci trace and put in some commands in the form that uses {} and ;.

Comments

Examples:

x = 3    -- this is a single line comment

    {- this is a 
          multiline
  comment -}

Module import

To import a module in a source code file, use the import statement. For example, to import module Random:

import Random

To import a module within the ghci interpreter, use the :m command. For example, to import module Random:

Prelude> :m Random
Prelude Random> 

Debugging within ghci

You can set breakpoints within do blocks by using the ghci along with the breakpoint command, which is in the GHC.Base module:

module Test where
import GHC.Base

main = do let a = 3
          let b = a*2
          breakpoint $ do
          print a+b

If you are using ghci 6.8.1 or later, you can use the more powerful debugging facilities included in ghci. See http://haskell.org/ghc/docs/6.8.1/html/users_guide/ghci-debugger.html for details.

Some special values: True, False, ()

True and False are the result of boolean expressions.

Another special value in Haskell is the "unit" value, written:

()

This is sometimes used to mean something like what is called "null" in other languages (in Haskell, null is used for something else; it's a boolean function that tells you if a list is empty).

Quick note on numerical negation and order of operations

By the way, sometimes you run into trouble because you expect the negation function ('-') to bind very tightly to numbers, but it doesn't. Just use parenthesis. Example:

Prelude> abs 3
3
Prelude> abs -3

<interactive>:1:5:
    No instance for (Num (a -> a))
      arising from the literal `3' at <interactive>:1:5
    Possible fix: add an instance declaration for (Num (a -> a))
    In the second argument of `(-)', namely `3'
    In the expression: abs - 3
    In the definition of `it': it = abs - 3
Prelude> abs (-3)
3

When we tried to do abs -3, what happened was that it was parsed as (abs -) 3. Oh well.

if/then/else

Example8