Doing Haskell: Part Two: Writing a REPL to do nothing!
Published by emacstheviking on Thu, 06/16/2011 - 21:51
SO, I sat and thought and thought and sat some more and tried to decide what would be a good introduction to doing something useful without repeating all those "hello world" programs or launching straight into to mathematical proof of how many UK politicians it takes to screw up a countries economic system. That would all be too dry.
Instead I came up with the idea of writing a real simple little program that behaves like a REPL, a "read-eval-print loop", familiar to LISP hackers and available for many other languages.
A REPL gets input from you, does something with it, outputs the results and then waits for more until you get fed up and terminate it with the appropriate command.
This is a tiny tiny program that apparently does very little but trust me, once we've gone through it in minute detail and smashed, bashed and hammered it around a bit you will have covered a lot of interesting things from just this tiny program: function composition, the (feared) IO monad, using libraries, currying, Haskell type inference, all sorts of weird and wonderful stuff.
REPL without a Cause...
Somebody has to say it. Initially it won't do much other than print out the available command and respond to the command "quit" to cause it to end. However, it gives me the opportunity to introduce you to a good spread of what you need to know to write Haskell programs so lets get stuck in. The full code is here if you want to read it verbatim or cut-and-paste save it.
From the top...
module Main where import System.Exit (exitSuccess) import System.IO (hPutStr, hFlush, hGetLine, stdin, stdout) import Data.Text (pack, unpack, toLower, toUpper)
- 1 -- tells "ghc" that we are creating a module called Main, and because we will be using --make it will also build us an executable in the process. See below.
- 3 -- this and the next two lines are how we tell "ghc" that we want to use a bunch of functions from other libraries. In this case we want one to cause the application to exit when requested and then some stuff to read and write to the console and final some text bashing functions, although as we'll see further on, these aren't strictly necessary, I've done it to avoid introducing "function composition" to early on, trying not to scare you!
In order to compile our file and create an executable all in one go, we will use the "ghc" compiler to do all the dirty work... for anything other than reasonably trivial programs one would normally use "cabal" but again, I am trying to keep it simple. Here's how we will always build our application:
$ ghc --make repl.hs [1 of 1] Compiling Main ( repl.hs, repl.o ) Linking repl ...
and to run it (on Linux at least) we need to use the "dot slash" notation as the folder we are in probably isn't on the command path. Presumably on Windows we'd end up with repl.exe in the same folder but I haven't had Windows in my house for a long time!
$ ./repl OBJITSU> help Available commands :- quit -- quit the REPL help -- show this help page OBJITSU> quit $
Of course, the four lines of code we covered won't do anything, not even compile, so to get to the point of being able to see the above example, lets look at the "main" entry point now.
The prompt at the start of the line is defined here, followed by the "main" function where the REPL life-cycle is started. As practice, change the text between the double-quotes (Haskell string delimiter) to something that feels good for you. Get used to doing little things that help build successes and confidence running the compiler and generally "making it your own".
prompt :: String
prompt = "OBJITSU> ";
main :: IO ()
main = do
hPutStr stdout prompt
hFlush stdout
cmd <- hGetLine stdin
let parts = words cmd
case (length parts) of
0 -> main
_ -> execCmd parts >> main
- 1 -- States that "prompt" is a function that has a return type of "String".
- 2 -- Defines a function that returns a string "OBJITSU >". Out prompt.
Ah say, ah say just a cotton picking minute there boy!
Now I know what you're thinking. "That"'s a function? No *&^%! way dude. I know functions. They have words like "function" before them and () and stuff like that. LMAO. One of the wonderful things about Haskell is just how clean it is to look at. Get used to it. You will soon hate PHP and all the excess characters you have to type just to match what you just saw. Hell. Let's do it and compare notes.
function prompt() {
return "OBJITSU >";
}
I won't compare character counts but I think you get the picture. "Hang on!", you say, "in PHP I wouldn't do it like that, I'd do it like this..."
define('prompt', "OBJITSU >");
Which is a lot shorter. Did you spot what just happened though? I will give you a clue. Haskell is called a "functional" language, can guess why? Well, you just changed a PHP function into a constant definition didn't you. It's no longer a function. ooOOOOOooo! In Haskell everything is a function and here's something interesting to make a note of: A function must have at least one argument or it's not a function, it's a constant. In functional programming, "function" means what it does in the true mathematical sense; the thing returns the same value for the same inputs. Stop and think about that. It's short, simple but oh so awesome in what it implies.
See-through Referees!
No no no. What he actually said was "Referential Transparency". No way dude. Way. Sweeeeeeeeet. etc. Um, what does that actually mean Mum? Well dear, it means that because the return value is the same all the time, it means that the compiler could quite happily replace all calls to the "prompt" function with the string "OBJITSU> " and the program would still work as it did before. In the PHP code, that ain't going to work!
This is important as it means that the compiler is free to do all sorts of grotesque things (but clever all the same) and it will still work. Parallelism is just a keyword away.
In the next article we'll have a closer look at the implementation of the "main" function...
Stay tuned.
Add new comment