
-----------------------------------
wtd
Thu Dec 23, 2004 11:26 pm

[Haskell-tut] A Gentler Introduction, with pictures! (New!)
-----------------------------------
This is a repost of a tutorial posted at compsci.ca.  Feel free to ask questions.  :)

Disclaimer

This is yet another attempt to bring the ideas of functional programming to the masses here, and an experiment in finding ways to make it easy and interesting to follow.

Your feedback would be good as a means of judging my progress.

Why should you care?

Functional programming is fundamentally a very different way of thinking about programming.  After all, we can learn several languages and pat ourselves on the back, but if the only real difference is syntactic, then we're not really being challenged.

What do I need?

You'll need either a Haskell interpreter or compiler.  For learning purposes the Hugs Haskell interpreter is fine.

You can download it from: A quick look at Hugs 

Start -> Programs -> Hugs98 -> Nov2003 -> Hugs (Haskell98 mode)

http://familygeek.com/haskell/hugs.png

So, at startup of Hugs we've got some ASCII art, copyright information, some basic usage tips, and a prompt.  This is a good start.

What can we do with this?

Well, we can evaluate expressions and see their results.  

What's an expression?

An expression is a little bit of code that takes one or more values and gives you a new value.

Consider this very simple example.

http://familygeek.com/haskell/hugs2.png

Moving on

But really, we could do basic math all day and be bored out of our skulls, so let's look at putting together a source file where we can build more complex things.

A Haskell source file is just a text file containing Haskell code.  The extension we use is ".hs".

So, what will our source file contain?  A simple hello world program.

module Main where

main = putStrLn "Hello, world!"

What do we have here?  

Well, first of all we have to deal with the fact that Haskell code is organized into modules.  Modules allow us to easily reuse code in other programs, and they allow the actual language itself to be relatively simple.  The name of the file should match the name of the module.  Here the module is named "Main".

Next we have the "main" function, the center of activity, as in many other programming languages.  The ease of creating this function shouldn't come as any surprise given the fact that Haskell focuses on functions.

putStrLn "Hello, world!"

Here we simply use the putStrLn function to print a string to the screen on its own line.  The similar putStr function does the same, but doesn't automatically skip to a new line.

http://familygeek.com/haskell/hugs3.png

Testing the code

So, how do we run the code in this file?

Well, open up your trusty Command Prompt window and "cd" to the directory where you saved your Main.hs file.

Once you're there, start Hugs by simply typing "hugs" and hitting enter.  Again we're back to:

http://familygeek.com/haskell/hugs.png

To load the "Main" module we simply:

Prelude> :load Main
Main>

The prompt has changed to indicate we're now in the Main module, rather than the Prelude module.

And to run the main function:

Main> main
Hello, world!

Main>

So, we've seen a little bit of Haskell

Is it scary?

If you say yes, that's not bad.  New things can be scary.  You'll get over it.

The real question, though, is: where do we go from here?

Well, since Haskell is a functional programming language, I'm thinking it might be good to see some more functions.

Expanding on Hello, world

Anyone even moderately familiar with my other tutorials will recognize the pattern of starting with a simple hello world program and then expanding upon it.

So, let's create a function "greet" which takes a name and greets that person.

Our Main.hs looked like:

module Main where

main = putStrLn "Hello, world!"

Now we're going to expand it with:

module Main where

main = greet "Bob"

greet name = putStrLn ("Hello, " ++ name ++ "!")

Of course, what if we want just the greeting string?

module Main where

main = greet "Bob"

greet name = putStrLn (greeting name)

greeting name = "Hello, " ++ name ++ "!"

Tidying the code up - Haskell tricks

Haskell by default infers the types of data being used in a program, but for documentation purposes, we can explicitly specify the types a function uses and returns.

Doing this, we write a type signature for main.  The "main" function does an IO action, and returns the closest thing you can get in Haskell to nothing, so:

module Main where

main :: IO ()
main = greet "Bob"

greet name = putStrLn (greeting name)

greeting name = "Hello, " ++ name ++ "!"

Double colons separate the name of a function and its signature.

Continuing, we generate a signature for the "greet" and "greeting" functions.

module Main where

main :: IO ()
main = greet "Bob"

greet :: String -> IO ()
greet name = putStrLn (greeting name)

greeting :: String -> String
greeting name = "Hello, " ++ name ++ "!"

These signatures are saying, "greet takes a string as an argument and does an IO operation," and, "greeting takes a string as an argument and returns another string."

greet name = putStrLn (greeting name)

Here we use parentheses because otherwise this would be seen as putStrLn taking two arguments, "greeting" and "name".  Since putStrLn only takes one argument, this would clearly be erroneous.

But the parentheses can get annoying, so we have the $ operator.  Essentially, the $ operator takes the value on its right hand side and gives it to the function on the left hand side.  So, now our greet function looks like:

greet name = putStrLn $ greeting name

So, our code now looks like:

module Main where

main :: IO ()
main = greet "Bob"

greet :: String -> IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting name = "Hello, " ++ name ++ "!"

Input as well as output

All of this greeting isn't very much good unless we can get input from the user as well, to find out their name.  

IO actions aren't quite like other functions.  To "chain" them together in sequence we use the keyword "do".

module Main where

main :: IO ()
main = do putStr "You are? "
          name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting name = "Hello, " ++ name ++ "!"

Probably the most immediately notiecable change is the use of indentation.  Haskell uses what's referred to as "layout", so semi-colons and braces aren't necessary.  They are available:

main = do { putStr "You are? ";
name  String
greeting name = 
  if name == "Haskell"
    then "Hey, whadda ya know?  This is a Haskell program!"
    else "Hello, " ++ name ++ "!"

This should look fairly straightforward to a programmer with basic experience in other languages.  Also, again we use "layout" to signify the structure of the conditional.

Case expressions

Let's say we want our program to greet "Matz" with, "You make a good language."  Using only "if":

module Main where

main :: IO ()
main = do putStr "You are? "
          name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting name = 
  if name == "Haskell"
    then "Hey, whadda ya know?  This is a Haskell program!"
    else if name == "Matz"
           then "You make a good language."
           else "Hello, " ++ name ++ "!"

Wow, that's ugly.  Fortunately, we have the case expression that should look familiar to programmers.

module Main where

main :: IO ()
main = do putStr "You are? "
          name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting name = 
  case name of
    "Haskell" -> "Hey, whadda ya know?  This is a Haskell program!"
    "Matz"    -> "You make a good language."
    otherwise -> "Hello, " ++ name ++ "!"

As with "if", we use layout.

Overloading functions

Of course, we can do this even more cleanly by overloading the greeting function.

module Main where

main :: IO ()
main = do putStr "You are? "
          name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

Loops

At this point you might be tempted to ask how Haskell handles looping, since that's a pretty basic thing for programmers to learn about in other languages.

Haskell provides no special syntax for looping.  All looping is achieved via recursion, where a function calls itself.

module Main where

main :: IO ()
main = do putStr "You are? "
          name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

A few questions that may come up from looking at this:

return ()

What does "return" do?  This basically turns () into IO (), which is the return our main function wants.
Why is "do" repeated in the "if" expression?  The "if" basically interrupts the chain of expressions we were creating.  To start another "chain" we need to use "do" again.


Lists

So, we can greet a number of people.  Of course, what if we want to be able to get a list of people we've greeted?

Well, we need a list.  A list in Haskell can contain any number of values, as long as they're all the same type.  The most basic list is an empty list:

[]

A small list of names might look like:

["Bob", "John"]

Anything dealing with such structures in other programming languages, where we often use the term "array", should instantly bring to mind loops.  Of course, we've already covered that.  Haskell has no explicit looping syntax, but rather recursion.  The solution, therefore is to find a way to define lists in a recursive manner.

Thankfully, Haskell lists are naturally recursive.  The : operator adds an element to the beginning of a list.  Our name list could look like:

"Bob" : "John" : []

Let's look at this in practice in a simple example.  A simple range function should create a list of numbers from a start to an end.

range s e = if s > e 
              then []
              else s : range (s + 1) e

This could look fairly cryptic until we break a sample use of it down.

range 1 5
1 : range 2 5
1 : 2 : range 3 5
1 : 2 : 3 : range 4 5
1 : 2 : 3 : 4 : range 5 5
1 : 2 : 3 : 4 : 5 : range 6 5
1 : 2 : 3 : 4 : 5 : []
1 : 2 : 3 : 4 : [5]
1 : 2 : 3 : [4, 5]
1 : 2 : [3, 4, 5]
1 : [2, 3, 4, 5]
[1, 2, 3, 4, 5]

Seeing a function with two arguments points out an interesting fact about Haskell.  Arguments to a function are simply separated by space, rather than commas, as in many other programming languages.

So, we might as well jump right in.

module Main where

main :: IO [String]
main = do putStr "You are? "
          name  String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

Breaking it down

As always, breaking a large complex program down into small, understandable components is essential to understanding.

main :: IO [String]

Our new signature for main indicates that it returns a list of strings.  Of course it remains IO "tainted".

return []

As before, if the user enters "quit", then we stop "looping".  This time, though, we return an empty list, much as we did in the range function.

nextRun  IO ()
printAll []     = return ()
printAll (x:xs) = do putStrLn x
                     printAll xs
                     
Here we've overloaded the printAll function so printing an empty list just returns ().  When I want to print an actual list, I use the pattern "(x:xs)".  We've seen the : before.  It's used when we're constructing lists.  So here x is the first element in the list.  The rest of the list is "xs", which can be read as the plural of "x".

Our code now looks like:

module Main where

main :: IO ()
main = do names  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

greetMultiple :: IO [String]
greetMultiple = do putStr "You are? "
                   name  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

greetMultiple :: IO [String]
greetMultiple = do putStr "You are? "
                   name  IO ()
printAll names = mapM_ putStrLn names

Much better.  We can improve this further, though.  In Haskell, if we have a function which takes two arguments, and only give it one of those arguments, we get another function which takes the final argument.

This is known as "partial application."  The partial application of functions allows us to easily create new functions based on already existing functions.  The printAll function can be rewritten as:

printAll :: [String] -> IO ()
printAll = mapM_ putStrLn

Similarly we can generate the greetings like so:

greetings :: [String] -> [String]
greetings = map greeting

Now, our code looks like:

module Main where

main :: IO ()
main = do names  IO ()
greet name = putStrLn $ greeting name

greeting :: String -> String
greeting "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
greeting "Matz"    = "You make a good language."
greeting name      = "Hello, " ++ name ++ "!"

greetMultiple :: IO [String]
greetMultiple = do putStr "You are? "
                   name  [String]
greetings = map greeting

Combining data

In many cases, data isn't as simple as just someone's name.  We often want different pieces of data grouped together into a single unit.  In Haskell we accomplish his via tuples.

So, with a name, let's say we want to store an age as well, so our greeting function can come up with greetings based on age as well.  An example tuple, then, might be:

("Bob", 49)

But, first, we need to be able to read an integer from the user.  Since everything read in is in the form of a string, we need a means to extract an integer from a string. The "reads" function steps in.  Now, reads returns something along the lines of:

[(Int, String)]

As a result we need to get the first element from the array, and the first item from the tuple.  The !! operator and fst function will do these things quite nicely.

So, a basic getAge function might look like:

getAge :: IO Integer         
getAge = do putStr "And you're how old? "
            input  IO ()
greet p = putStrLn $ greeting p
                           
printAll :: [String] -> IO ()
printAll = mapM_ putStrLn

greetings :: [PersonInfo] -> [String]
greetings = map greeting
         
getAge :: IO Int
getAge = do putStr "And you're how old? "
            input  String

This requires that the argument be of type PersonInfo.  Given this, we can't simply define greeting to take an argument of type DogInfo.  What we want instead is to classify data types based on whether or not we can greet them.

So, let's create a class that does just that.

class Greetable a where
  greeting :: a -> String

This is fairly straightforward.  Here "a" represents any type in the Greetable class.  Let's add a few more functions related to the first.

class Greetable a where
  greeting  :: a -> String
  greetings :: [a] -> [String]
  greet     :: a -> IO ()

Now, the interesting thing about classes in Haskell is that not only can we specify the types of these functions, but if we have related functions which simply depend upon another, we can provide a default definition.  Now, when we later define greeting, we get the other two automatically.

class Greetable a where
  -- declarations
  greeting  :: a -> String
  greetings :: [a] -> [String]
  greet     :: a -> IO ()
  -- default definitions
  greetings  = map greeting
  greet name = putStrLn $ greeting name

One thing to notice is that I've used comments for the first time.  Anything following -- on a line is a comment in Haskell.

Instances

A data type is admitted to a class by means of an instance declaration.  This is where we define the functions required by the class.  To admit the PersonInfo data type into the Greetable class.

instance Greetable PersonInfo where
  greeting (Person name age) 
    | age < 12          = "Do your parents know where you are, "  ++ name ++ "?"
    | age > 80          = "Do your children know where you are, " ++ name ++ "?"
    | name == "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
    | name == "Matz"    = "You make a good language."
    | otherwise         = "Hello, " ++ name ++ "!"
    
This looks pretty similar to our previous definition of greeting, so there's not a lot new to learn here.

So, now our existing program looks like:

module Greeting where
          
data PersonInfo = Person String Int 
  deriving (Show, Eq)

class Greetable a where
  -- declarations
  greeting  :: a -> String
  greetings :: [a] -> [String]
  greet     :: a -> IO ()
  -- default definitions
  greetings =  map greeting
  greet x   =  putStrLn $ greeting x

instance Greetable PersonInfo where
  greeting (Person name age) 
    | age < 12          = "Do your parents know where you are, "  ++ name ++ "?"
    | age > 80          = "Do your children know where you are, " ++ name ++ "?"
    | name == "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
    | name == "Matz"    = "You make a good language."
    | otherwise         = "Hello, " ++ name ++ "!"
                     
gatherNames :: IO [String]
gatherNames = do putStr "You are? "
                 name  greeter

And we see something rather odd.  Nothing.

Why don't we see anything?

Well, it's a petty little thing which has nothing to do with functional programming.  The problem is that Haskell programs compiled with GHC don't output anything until a newline is entered.  When we ask, "You are?", we don't go to a new line, so nothing gets printed to the screen.

The fix is simple.  We need to flush all of the text in standard output.  For this we'll need to import the IO module, then flush standard output after every instance of putStr.  The putStrLn function doesn't have this problem.

With this taken into mind, our Greeting module now looks like:

module Greeting where
          
import qualified Char (toLower)  
import IO
          
data PersonInfo = Person String Int 

class Greetable a where
  -- declarations
  greeting  :: a -> String
  greetings :: [a] -> [String]
  greet     :: a -> IO ()
  -- default definitions
  greetings =  map greeting
  greet x   =  putStrLn $ greeting x

instance Greetable PersonInfo where
  greeting (Person name age) 
    | age < 12          = "Do your parents know where you are, "  ++ name ++ "?"
    | age > 80          = "Do your children know where you are, " ++ name ++ "?"
    | name == "Haskell" = "Hey, whadda ya know?  This is a Haskell program!"
    | name == "Matz"    = "You make a good language."
    | otherwise         = "Hello, " ++ name ++ "!"
                           
printAll :: [String] -> IO ()
printAll = mapM_ putStrLn
         
getAge :: IO Int
getAge = do putStr "And you're how old? "
            hFlush stdout
            input  putStrLn $ "Failure: " ++ show x
          system "pause"
          
Now, I mentioned getting variables passed to the program.  The getArgs function will do that, in the form of a list of strings.  So, let's greet the names passed to a program.

module Main where

import System

main :: IO ExitCode
main = do names 