Misc Links
Forum Archive
News Archive
File DB
 

Ads
 

Advertisement
 

Latest Forum Topics
wow 56 k modems are
Posted by Red Squirrel
on Oct 14 2013, 11:52:23 pm

I Need A Program
Posted by rovingcowboy
on Sep 23 2013, 5:37:59 pm

having trouble witn lan
Posted by rovingcowboy
on Sep 23 2013, 5:40:56 pm

new problem for me
Posted by rovingcowboy
on Sep 23 2013, 5:54:09 pm

RBC Royal Bank
Posted by Red Squirrel
on Aug 13 2013, 6:48:08 pm

 

Introduction to Haskell
Functional programming with Haskell
By Chris Dutton


Modules

So, we've seen input, output, strings, functions, conditionals, lists, tuples, and some very handy functions like map and mapM_. What's next?

Well, looking at the above program, most of it handles greeting people, and then one lonely function is the main function where everything happens. What if I want to use the functions related to greeting in another program but I want a different main?

Well, then I need to put all of those functions into their own module. Let's call the new module Greeting. Naturally, it'll be located in the file Greeting.hs.

module Greeting where

greet :: (String,Int) -> IO ()
greet name = putStrLn $ greeting name

gatherNames :: IO [String]
gatherNames = do putStr "You are? "
                 name <- getLine
                 if name == "quit"
                   then return []
                   else do otherNames <- gatherNames
                           return $ name : otherNames

printAll :: [String] -> IO ()
printAll = mapM_ putStrLn

greetings :: [(String,Int)] -> [String]
greetings = map greeting

getAge :: IO Int
getAge = do putStr "And you're how old? "
            input <- getLine
            let parsed = reads input
            if parsed == []
              then do putStrLn "I'm sorry, but could you repeat that?"
                      getAge
              else return $ fst $ parsed !! 0

gatherInfo :: IO [(String, Int)]
gatherInfo = do putStr "You are? "
                name <- getLine
                if name == "quit"
                  then return []
                  else do age <- getAge
                          let info = (name,age)
                          otherInfo <- gatherInfo
                          return $ info : otherInfo

greeting :: (String,Int) -> String
greeting (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 ++ "!"

No our Main module simply looks like:

module Main where

import Greeting

main :: IO ()
main = do info <- gatherInfo
          printAll $ greetings info

Of course, if you want it to be explicit where the functions you're using come from, you can prepend the name of the module.

module Main where

import Greeting

main :: IO ()
main = do info <- Greeting.gatherInfo
          Greeting.printAll $ Greeting.greetings info

This can be encorced by using the "qualified" import modifier.

module Main where

import qualified Greeting

main :: IO ()
main = do info <- Greeting.gatherInfo
          Greeting.printAll $ Greeting.greetings info

And with either we can limit the functions we import.

module Main where

import Greeting (gatherInfo, printAll, greetings)

main :: IO ()
main = do info <- gatherInfo
          printAll $ greetings info

Introducing our own data types

Of course, at this point, we've used a new data type in the form of a tuple. However, we're counting on being able to recognize that a tuple consisting of a string and an integer is a person.

Haskell gives us the power to be more expressive, by introducing new data types. In this case it's really quite simple.

data PersonInfo = Person String Int

This introduces a new type called PersonInfo with a single constructor which takes a string and an int. So, let's start by simply redefining the greeting function to take advantage of this new data type.

greeting :: PersonInfo -> String
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 ++ "!"

And, modifying the rest of our code to use this new data type, we end up with:

module Greeting where

data PersonInfo = Person String Int

greet :: PersonInfo -> 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 <- getLine
            let parsed = reads input
            if parsed == []
              then do putStrLn "I'm sorry, but could you repeat that?"
                      getAge
              else return $ fst $ parsed !! 0

gatherInfo :: IO [PersonInfo]
gatherInfo = do putStr "You are? "
                name <- getLine
                if name == "quit"
                  then return []
                  else do age <- getAge
                          let info = Person name age
                          otherInfo <- gatherInfo
                          return $ info : otherInfo

greeting :: PersonInfo -> String
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 ++ "!"

To arrive at this, I made very few changes to the code I had previously. This should demonstrate quite nicely the expressive power of Haskell.

Greeting other things

Now, we've defined a set of functions useful for greeting a person. However, people are not the only things we may want to greet. Consider, for instance, the case where we want to greet a dog. For the purposes of our program a dog will be described by its name, build and color. Based on these things we'll formulate a greeting.

First thing's first, though. Let's define the data types we'll need.

data Build = Skinny | Medium | Fat
data Color = White | Black | Gray | Red | Brown
data DogInfo = Dog String Build Color

In the build and Color data types, we have a set of constructors which take no arguments. Any one of these constructors creates a value of type Build or Color. This is somewhat analogous to the idea of enumerated types or "enums" in other languages.

The Dog constructor then uses these data types.

Let's talk about classes

For the purposes of this document, please foget what you know about classes in object-oriented languages like C++, Java, C#, Python, Ruby, Eiffel, etc. When we talk about classes in Haskell, we're talking about classifying data types, according to what functions we can use on them.

The advantage of classifying data types in this way is that we don't need to be as specific when declaring a function. Consider my current declaration of the greeting function.

greeting :: PersonInfo -> 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 <- getLine
                 if name == "quit"
                   then return []
                   else do otherNames <- gatherNames
                           return $ name : otherNames

printAll :: [String] -> IO ()
printAll = mapM_ putStrLn

getAge :: IO Int
getAge = do putStr "And you're how old? "
            input <- getLine
            let parsed = reads input
            if parsed == []
              then do putStrLn "I'm sorry, but could you repeat that?"
                      getAge
              else return $ fst $ parsed !! 0

gatherInfo :: IO [PersonInfo]
gatherInfo = do putStr "You are? "
                name <- getLine
                if name == "quit"
                  then return []
                  else do age <- getAge
                          let info = Person name age
                          otherInfo <- gatherInfo
                          return $ info : otherInfo

Of course, none of these changes influences our Main module in the slightest.

Sidetracking: the Show class

So we've seen one class of our own design. There are other classes already in Haskell 98. One useful class is the Show class, which requires that the function "show" be defined. That class would look like:

class Show a where
  show :: a -> String

This provides a convenient means to get a string representation of something. For instance, to print an int, we'd use something like the following, since putStrLn can only deal with strings.

putStrLn $ show 42

Or to add an integer to a string:

"Foo " ++ show 42

Let's consider a more pertinent situation. Previously, we had defined a data type Color.

data Color = White | Black | Gray | Red | Brown

Now, at some point we might wish to actually get a string containing "White" or "Red". We can achieve this by making Color an instance of Show.

instance Show Color where
  show White = "White"
  show Black = "Black"
  show Gray  = "Gray"
  show Red   = "Red"
  show Brown = "Brown"

It seems like there should be an easier way to achieve such a simple transformation. Well, for such a basic class, we can make Color a member of the Show class without an instance declaration.

data Color = White | Black | Gray | Red | Brown
  deriving (Show)

Of course, in the course of this program we might want "red" rather than "Red". Fortunately, we can simply use map in this case. A string in Haskell, you see, is simply a list of characters.

First, though, we have to import the toLower function from the Char module.

import qualified Char (toLower)

Then we can easily apply the map.

map Char.toLower $ show Red

Back on topic: greeting a dog

So, we have a Greetable class that requires members of the class define a greeting function and gives us a couple of free functions if we do.

class Greetable a where
  -- declarations
  greeting  :: a -> String
  greetings :: [a] -> [String]
  greet     :: a -> IO ()
  -- default definitions
  greetings  = map greeting
  greet name = putStrLn $ greeting name

And we have a few new data types.

data Build = Skinny | Medium | Fat
data Color = White | Black | Gray | Red | Brown deriving (Show)
data DogInfo = Dog String Build Color

What we need is to make DogInfo an instance of Greetable.

instance Greetable DogInfo where
  greeting (Dog _    Skinny _    ) = "There's hardly anything to that dog."
  greeting (Dog _    Fat    _    ) = "What a fat dog!"
  greeting (Dog name _      color) = "What a healthy " ++
                                     (map Char.toLower $ show color) ++
                                     " dog.  Hello, " ++ name ++ "!"

This example shows one important new feature of Haskell. Namely, what's the deal with all of the underscores?

Well, in Haskell, when matching a pattern, you have to include all of the arguments. Of course, sometimes you just don't care what the argument is. When we greet a skinny or fat dog, we're rude and don't care what the dog's name or color is. We're just going to insult it anyway.

When we don't care about an argument, we use an underscore. This signifies that yes there's an argument there, but we don't care enough to give it a name.

In the end, I have the following program.

module Greeting where

import qualified Char (toLower)

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? "
            input <- getLine
            let parsed = reads input
            if parsed == []
              then do putStrLn "I'm sorry, but could you repeat that?"
                      getAge
              else return $ fst $ parsed !! 0

gatherInfo :: IO [PersonInfo]
gatherInfo = do putStr "You are? "
                name <- getLine
                if name == "quit"
                  then return []
                  else do age <- getAge
                          let info = Person name age
                          otherInfo <- gatherInfo
                          return $ info : otherInfo

data Build = Skinny | Medium | Fat
data Color = White | Black | Gray | Red | Brown deriving (Show)
data DogInfo = Dog String Build Color

instance Greetable DogInfo where
  greeting (Dog _    Skinny _    ) = "There's hardly anything to that dog."
  greeting (Dog _    Fat    _    ) = "What a fat dog!"
  greeting (Dog name _      color) = "What a healthy " ++
                                     (map Char.toLower $ show color) ++
                                     " dog.  Hello, " ++ name ++ "!"

The end result

At this point, I can greet either a person or a dog using the same function, even though the two share nothing in common except the greeting function. Of course, what more do they really need to share?

do greet $ Person "John" 19
   greet $ Dog "Fido" Medium Brown

And we see on the screen:

Hello, John!
What a healthy brown dog.  Hello, Fido!






Next Page
spacer
30491 Hits Pages: [1] [2] [3] [4] [5] [6] 6 Comments
spacer


Latest comments (newest first)
Posted by genea on December 12th 2005 (13:14)
Very good article, with the part about type classes and the ability for a data type derived from a given class being able to automatically inherit the method (functions) from the class definition. I guess I missed that in the mountain of literature I had read and had been up till now missing the benefit of this info... I liked dobbing about with HASKELL and this just makes me believe, that it will have a bright future ahead, or one of the languages derived from it, such as CLEAN.
Thanks again.. and I too would like to see the next six!!
gene

spacer
Posted by wtd on June 06th 2005 (19:10)
QUOTE (HHH @ Jun 4 2005, 12:40 PM)
Hi,
how can I do a line skip in a String. I mean that one String ouput looks like this:

Hello, (line skip)
....

CODE
putStrLn "Hello"


The "Ln" means "line".

If you just want to skip a line...

CODE
putStrLn ""


Or you could make that a function.

CODE
skipLine = putStrLn ""

spacer
Posted by wtd on January 01th 2005 (16:44)
QUOTE (fred laforge @ Jan 11 2005, 10:03 AM)
great tutorial -- when will you post the next 6 lessons? :-)

Heh. That one took a bit out of me, so there might just be smaller updates for a while. smile.gif.

spacer
Posted by wtd on January 01th 2005 (02:12)
In my introduction to Haskell I createded a simple function greeting, which takes a name as a string and formulates a greeting.

CODE
greeting :: String -> String
greeting name = "Hello, " ++ name ++ "!"


And then, I created a greet function which takes a name and prints the greeting for it.

CODE
greet :: String -> IO ()
greet name = putStrLn (greeting name)


The latter function I rewrote as:

CODE
greet :: String -> IO ()
greet name = putStrLn $ greeting name


Now, the argument "name" should appear quite redundant in that last example. It is. What if, instead, we could simply combine the two functions, "greeting" and "putStrLn".

Haskell provides an easy mechanism for doing so.

CODE
greet :: String -> IO ()
greet = putStrLn . greeting


Now, we can make a similar observation about the greeting function.

CODE
greeting :: String -> String
greeting name = "Hello, " ++ name ++ "!"


This is really two functions, since operators are just functions. Given the order of evaluation, this could be rewritten:

CODE
greeting :: String -> String
greeting name = "Hello, " ++ (name ++ "!")


Now, since we can partially apply functions - give them one argument, and get back a function which takes another argument and gives us the rest - we can rewrite this as:

CODE
greeting :: String -> String
greeting = ("Hello, " ++) . (++ "!")


Just as in the original, the function takes a string, appends "!" to it, then prepends "Hello, " to that.

spacer
Posted by wtd on January 01th 2005 (19:54)
Oh, and when you get around to writing something in Haskell, and you have questions, feel free to ask. smile.gif
spacer
View all comments
Post comment


Top Articles Latest Articles
- What are .bin files for? (669062 reads)
- Text searching in linux with grep (161180 reads)
- Big Brother and Ndisuio.sys (150471 reads)
- PSP User's Guide (139547 reads)
- SPFDisk (Special Fdisk) Partition Manager (117240 reads)
- How to Use MDADM Linux Raid (188 reads)
- What is Cloud Computing? (1225 reads)
- Dynamic Forum Signatures (version 2) (8769 reads)
- Successfully Hacking your iPhone or iTouch (18714 reads)
- Ultima Online Newbie Guide (35906 reads)
corner image

This site best viewed in a W3C standard browser at 800*600 or higher
Site design by Red Squirrel | Contact
© Copyright 2019 Ryan Auclair/IceTeks, All rights reserved