Functional programming with Haskell
By Chris Dutton
At this point you might be thinking that this is all well and good, but this method of interpretation, where we write the code, then test it is tedious and not really suited to writing a program that can run, do its thing, then quit.
Fortunately, Haskell can be compiled to native code as well as interpreted.
The compiler we'll use is the Glorious Glasgow Haskell Compilation System, or just GHC, available at:
An installer for Windows is available at:
To compile the program we've assembled, the command is:
prompt> ghc -O --make Main -o greeter.exe
The -O tells the compiler to perform optimizations, --make tells the compiler we're actually building an executable based on this module, and -o tells the compiler the name of the resulting executable.
Now, we can run this program.
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 <- 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? " hFlush stdout 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 ++ "!"
Now, repeating the above mentioned compiling will give us an executable that does the right thing.
More on executables
Now that we have the ability to compile a Haskell program to a native executable, there are a couple of things we might want to do.
Both are provided by functions in the System module. The first is solved by the system function. The most useful example of this is probably to run the Windows "pause" program, which creates that famous "press any key to continue..." message.
This is useful for preventing a console window from instantly closing when the program has finished its work. If I compile and run the following by double-clicking on the resulting executable, t'll print the message, but it'll do it so quick that the window will close before I can see anything.
module Main where main :: IO () main = putStrLn "Hello, world!"
Instead, I want to pause at the end of the program, so I compile:
module Main where import System main :: IO ExitCode main = do putStrLn "Hello, world!" system "pause"
Interestingly, the system function returns an ExitCode, so you can tell if the program you ran was successful or not. If it was successful, ExitSuccess is returned. Otherwise, ExitFailure is returned. The ExitFailure constructor also takes an int, so you can tell exactly what code the program exited with.
We could test the success of pause.
module Main where import System main :: IO ExitCode main = do putStrLn "Hello, world!" result <- system "pause" case result of ExitSuccess -> putStrLn "It worked." ExitFailure x -> 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 <- getArgs let greetings = map greeting names printAll = mapM_ putStrLn greeting n = "Hello, " ++ name ++ "!" printAll greetings system "pause"
And now for something completely different
Let's build a quick replacement for the typical copy program, which takes one file and copies its contents into another file.
This should cover:
To get at the file handling functions, we'll need the IO module.
Let's take a look at opening a file and reading the contents, then printing them to the screen.
module Main where import IO main :: IO () main = do fh <- openFile "bar.txt" ReadMode text <- hGetContents fh hClose fh putStrLn text
openFile "bar.txt" ReadMode
The first argument is obviously the name of the file to open. The second argument is the mode in which to open the file. We could have also chosen WriteMode, AppendMode, or ReadWriteMode.
Obviously this gets the contents of "bar.txt".
Here we're closing the file.
So, now we can open the file, get the contents, and close the file. The next goal is to be able to open a file, write to it, and close it.
module Main where import IO main :: IO () main = do fh <- openFile "foo.txt" WriteMode hPutStr fh "Hello\nworld!" hFlush fh hClose fh
There's not a whole lot new here.
Simplifying the handling of files
But if this is all we're doing with files, we can replace all of that with two functions: readFile and writeFile.
So, let's write a program that copies one file's contents into another file.
module Main where import IO main :: IO () main = do text <- readFile "foo.txt" writeFile "bar.txt" text
Of course, to more accurately emulate the copy command, our program should work on user-supplied filenames, rather than hard-coded ones.
module Main where import IO import System main :: IO () main = do args <- getArgs let i = args !! 0 o = args !! 1 text <- readFile i writeFile o text
If we supply less than two arguments, this will fail due to "o = args !! 1". So we should check to see if there are at least two arguments. If not, remind the user.
module Main where import IO import System main :: IO () main = do args <- getArgs if length args >= 2 then let i = args !! 0 o = args !! 1 text <- readFile i writeFile o text else hPutStrLn stderr "This program needs at least two arguments."
Now, if we try to open a file for reading that doesn't exist, our program will raise an error and terminate. We could try to prevent this, but that's pretty tedious, so instead we'll let the error happen and "catch" it, using, appropriately enough, the catch function.
Our desired behavior is, if the file to be read doesn't exist, the text should be an empty string. The catch functions allows us to do that by providing a function to run if an error occurs.
module Main where import IO import System main :: IO () main = do args <- getArgs if length args >= 2 then let i = args !! 0 o = args !! 1 text <- catch (readFile i) (\_ -> return "") writeFile o text else hPutStrLn stderr "This program needs at least two arguments."
The only thing that should look odd here is:
\_ -> return ""
What we're seeing here is another way of writing a function. The backslash introduces the function's arguments, and the -> leads to the actual guts of the function. The argument in this case is the error itself, but we aren't concerned with what the error actually is, so we use an underscore. The function itself simply returns an empty string.
One last thing
In the last code sample, we saw:
catch (readFile i) (\_ -> return "")
What you might also see is the use of:
readFile i `catch` \_ -> return ""
Here the backticks around catch turn a function with two arguments into an operator.
Which of these two forms you use is purely a matter of style, but it's important to understand either.
This site best viewed in a W3C standard browser at 800*600 or higher
Site design by Red Squirrel | Contact
© Copyright 2021 Ryan Auclair/IceTeks, All rights reserved