|
|
Send spam to: website@xeonlive.com nick@xeonlive.com georgiapeach1241@aol.com Functional programming with Haskell By Chris Dutton There's a simpler wayThe task of applying a function to each element in a list is such a common one, you'd think there would be a function or functions already present to solve this problem. When we're applying a normal function, we can use "map". Let's say we want to double each number in a list. timesTwo x = x * 2 map timesTwo [1,2,3,4] Now, when we're using IO "actions", we can't use map, but rather we use the mapM_ function. module Main where main :: IO () main = do names <- greetMultiple putStrLn "I greeted:" mapM_ putStrLn names greet :: String -> 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 <- getLine if name == "quit" then return [] else do greet name nextRun <- greetMultiple return $ name : nextRun A different greeting approachNow, lets say we first want to gather names from a group and then greet them all. First we need a gatherNames function. gatherNames :: IO [String] gatherNames = do putStr "You are? " name <- getLine if name == "quit" then return [] else do otherNames <- gatherNames return $ name : otherNames Now we can incorporate that into our program and use map to generate the appropriate greetings, then pass the result of that with $ to the "mapM_ putStrLn" we've already seen. module Main where main :: IO () main = do names <- gatherNames mapM_ putStrLn $ map greeting names greet :: String -> 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 ++ "!" gatherNames :: IO [String] gatherNames = do putStr "You are? " name <- getLine if name == "quit" then return [] else do otherNames <- gatherNames return $ name : otherNames Back to printAllmapM_ putStrLn names Of course we may look at this and think it's uglier in use than: printAll names But previously we've seen that the definition of printAll adds quite a bit of unnecessary code. So, let's redefine printAll. printAll :: [String] -> 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 <- gatherNames printAll $ greetings names greet :: String -> 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 <- getLine if name == "quit" then return [] else do greet name nextRun <- greetMultiple return $ name : nextRun 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] -> [String] greetings = map greeting Combining dataIn 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 <- getLine return $ fst $ reads input !! 0 There's just one problem with this, and it's a big one. What if someone gives you bad input? Well, then reads will gives us am empty list, and that makes "reads input !! 0" cause an error. We need a way to avoid the error in the first place. We can do so by adding in a conditional expression which checks to see if the list is empty. Of course, if it is, we should probably tell the user we didn't get their age, and ask them again. Recursion will serve us well here. 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 Taking as step back againWe want to not only get names, but also their ages. Where we had a gatherNames function before, let's replace that with a gatherInfo function. 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 The basic form looks pretty much like other recursive functions we've defined. Now, even though we have a very different type of data here than a simple string, we can get the string from each piece of info with map and fst. module Main where main :: IO () main = do info <- gatherInfo let names = map fst info printAll $ greetings names 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 ++ "!" printAll :: [String] -> IO () printAll = mapM_ putStrLn greetings :: [String] -> [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 Doing something with the extra informationNow we have both name and age info for each of the people we're greeting, and we can still greet people the old way, but that seems to be missing the point of having that extra information in the first place. So, we want to get different greetings depending on age, as well as name. So, we'll first want to remake the greeting function. Our old greeting function looked like: 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 ++ "!" The first thing we realize will have to change is the signature of this function. greeting :: (String,Int) -> String Now, for the rest, if the age is less than twelve, we'll answer with: "Do your parents know where you are, To this end we'll use "guards" to define multiple versions of the function for these different conditions. The syntax should be fairly obvious.
So, our entire program is now:
|
![]() |
This site best viewed in a W3C standard browser at 800*600 or higher Site design by Red Squirrel | Contact © Copyright 2010 Ryan Auclair/IceTeks, All rights reserved |