Rather than being a tutorial proper, this post summarizes my recent forays into Haskell, pointing to documentation that I found particularly useful in the order in which I think it would have helped me most. This is followed by the implementation and extensive discussion of a simple, four-line shell utility. As such, I hope it will quickly get you started on real stuff, such as unix shell tools, supplying you with the information necessary to know where to look when you encounter a problem.
I've been meddling with functional languages on and off for quite some time now. I originally got hooked via Mathematica and have since been looking for a free replacement. I looked at various lisps, and some of its step-children, but nothing seemed to quite work for me (or the other way around) -- I kept falling back to Python (my mother tongue from way back), adopting an increasingly functional style but feeling oddly dissatisfied.
Haskell seemed like a good way forward -- a supportive community, rapidly progressing development, real applications that are actually being used, combined with elegance, purity and all the goodies that come with it. Like a type-checked lisp without the braces or the more questionable members of its community.
However, this purity came at a price. And in this case, the price had a funny name: Monads. What initially made this a show-stopper for me was that (i) you need them, and (ii) there are so many bad monad tutorials that it's difficult to find the truly excellent ones (which fortunately do exist). With those at hand, there was no need to be afraid.
foo :: String -> String(this one defines foo as being an expression that maps a String to another String), the type inspection operation :t
Prelude Main> "Hello World" "Hello World" Prelude Main> :t "Hello World" "Hello World" :: [Char](strings are lists of characters), the conversion to a string representation
Prelude Main> show 5 "5"the loading of modules with the load function
Prelude> :load sumLines.hs Ok, modules loaded: Main.(they can be reloaded with :r).
Continue your splurge with the operators cons (:)
Prelude Main> [1, 2] [1,2] Prelude Main> 1 : [2] [1,2]range [x..y]
Prelude Main> [1..10] [1,2,3,4,5,6,7,8,9,10]map,
Prelude Main> map reverse ["foo", "bar"] ["oof","rab"]foldr,
Prelude Main> foldr (+) 0 [1, 2] 3function composition (.)
Prelude> let nop = reverse . reverse Prelude> nop "baz" "baz"and function application ($)
Prelude> show $ sum [1, 2] "3"If you leave out the $ in the above example, the interpreter will fail with an interesting result
Prelude> show sum [1, 2]suggesting that it always contains the last result::1:0: Couldn't match expected type `[t1] -> t' against inferred type `String' In the expression: show sum [1, 2] In the definition of `it': it = show sum [1, 2]
Prelude> sum [1, 2] 3 Prelude> show it "3"
I should stress (because overlooking it caused me some grief), that code that is valid as part of a module may be invalid when you type it at the interpreter prompt. Note how the tutorial carefully distinguishes green stuff (``Code'', i.e. separate modules) from blue stuff (``Interaction'', i.e. stuff typed at the interpreter prompt). Consider (copied from p. 22 in the tutorial):
Prelude Main> square x = x * xor:1:9: parse error on input `='
Prelude Main> f :: String -> StringThe tutorial briefly mentions this in section 3.8, but does not delve much deeper. For definitions, I found that this works::1:0: Not in scope: `f'
Prelude Main> let square x = x * x Prelude Main> square 5 25the corresponding documentation is unfortunately a little sparse (if you find something better, please mention them here).
At this point, we're essentially there, except we're missing this concept called ``Monads'', which seems as crucial (needed for I/O and state) as it seems elusive. Haskell tutorials at this point will typically note how very abstract they are and then attempt to see you off with a weird metaphor and a link to the wikipedia entry on category theory. Don't let them distract you, instead proceed to let sigfpe convince you that you could have invented them.
With that, we can declare and evaluate expressions, and are confident enough about monads to tackle I/O, so we should be in excellent shape for
First off, let me note that this whole post is literate haskell, so you can save, compile and run it without further ado. While this is probably one of Haskell's most trivial features, I have to say it's one of those that I found the most attractive (and indicative of the Haskell community's overall attitude).
Anyhow.
For a shell tool (which is what we want to write here), we would like to read some stuff from standard input, do something with it and write the result to standard output. This action is performed most concisely using the interact function. You might want to head over to Hoogle, where you can search the online Haskell documentation based on keywords and types. We find that
The interact function takes a function of type String->String as its argument. The entire input from the standard input device is passed to this function as its argument, and the resulting string is output on the standard output device.Note that while this is very convenient, it seems to take the entire input in a non-lazy way, making it unsuitable for processing large streams (at least the example below crashed with a heap overflow on large files). Still we will use it here for the sake of simplicity. More scalable functions for performing the task in a similar way are found on the same page. I would recommend that you skim the prelude documentation page anyhow, to familiarize yourself with what's available, as well as getting a bit of exposure to Haskell's syntax.
Let's start. A Haskell program requires a main function (if you intend to copy this code, remember not to type this into the interpreter, but to write it in a separate module, and to leave out the ">" at the beginnings of the line)
> main = interact getResultwhere getResult is an as yet unspecified function that takes an input string and produces an output string. Let's pretend that we want to write a program that takes a file containing one number per line, and prints out the sum of these numbers. We define getResult appropriately:
> getResult ss = (show $ sum $ floatList ss) ++ "\n"Here, ss is the input string, sum, ++ (click the links for Hoogle docs) are builtin functions that respectively sum a list of numbers or concatenate lists (strings being lists of characters, remember?). Finally, we need to convert the input string to a list of numbers, which we do with the as yet undefined function floatList
> floatList ss = map readFloat $ lines sswhere lines is a function that breaks a string at newlines, and readFloat can be defined as
> readFloat x = read x :: Floati.e. performing the read operation with an explicit cast to Float.
Done! You can save (file ending .lhs, make sure the ">" don't get escaped), compile and run this document!
At this point, you should be comfortable browsing Don Stewart's slightly more demanding tutorials. Since this post is essentially a cheap ripoff of his collection of simple unix tools implemented as elegant Haskell one-liners, I would continue there and then proceed to e.g. his implementation of a complete cat (an efficient implementation including proper argument parsing). Enjoy!