Last modified on 5 July 2014, at 18:38

Write Yourself a Scheme in 48 Hours/First Steps


Write Yourself a Scheme in 48 Hours
First Steps

First, you'll need to install GHC. On Linux, it's often pre-installed or available via the package manager (apt-get or yum for example, depending on your distribution). It's also downloadable from http://www.haskell.org/ghc/. A binary package is probably easiest, unless you really know what you're doing. It should download and install like any other software package. This tutorial was developed on Linux, but everything should also work on Windows, as long as you know how to use the command line, or on the Macintosh from within the Terminal.

For UNIX (or Windows Emacs) users, there is a pretty good Emacs mode, including syntax highlighting and automatic indentation. Windows users can use Notepad or any other text editor: Haskell syntax is fairly Notepad-friendly, though you have to be careful with the indentation. Eclipse users might want to try the eclipsefp plug-in.

Now, it's time for your first Haskell program. This program will read a name off the command line and then print a greeting. Create a file ending in ".hs" and type the following text. Be sure to get the indentation right, or else it may not compile.

 module Main where
 import System.Environment
 
 main :: IO ()
 main = do
     args <- getArgs
     putStrLn ("Hello, " ++ args !! 0)

Let's go through this code. The first two lines specify that we'll be creating a module named Main that imports the System module. Every Haskell program begins with an action called main in a module named Main. That module may import others, but it must be present for the compiler to generate an executable file. Haskell is case-sensitive: module names are always capitalized, definitions always uncapitalized.

The line main :: IO () is a type declaration: it says that main is of type IO (), which is an IO action carrying along values of unit type (). A unit type allows only one value, also denoted (), thus holding no information. Type declarations in Haskell are optional: the compiler figures them out automatically, and only complains if they differ from what you've specified. In this tutorial, I specify the types of all declarations explicitly, for clarity. If you're following along at home, you may want to omit them, because it's less to change as we build our program.

The IO type is an instance of something called a monad, which is a scary name for a not-so-scary concept. Basically, a monad is a way of saying "we'll be carrying along and combining, in some specific manner, values with some extra information attached to them, which most functions don't need to worry about". How we carry along this extra information and combine our values is what makes the particular Monad type to be what it is; underlying values might get changed and converted from one type into another by regular functions (invoked by actions), oblivious to the extra stuff that goes on around them, but the "pipe" (the value-propagation mechanism) stays the same.

In this example, the "extra information" is the IO actions to be performed using the carried along values, and the final basic value is nothing, represented as (). Both IO [String] and IO () belong to the same IO monad type with different basic types, meaning they are IO actions acting upon, and passing along values of different types, [String] and (). Monadic values thus combined with the basic values packaged inside them are often called "actions", because the easiest way to think about the IO monad is a sequencing of actions each potentially acting on the passed along basic values, affecting the outside world.

Haskell is a functional language: instead of giving the computer a sequence of instructions to carry out, you give it a collection of definitions that tell it how to perform every function it might need. These definitions use various compositions of actions and functions. The compiler figures out an execution path that puts everything together.

To write one of these definitions, you set it up as an equation. The left hand side defines a name, and optionally one or more patterns (explained later) that will bind variables. The right hand side defines some composition of other definitions that tells the computer what to do when it encounters the name. These equations behave just like ordinary equations in algebra: you can always substitute the right hand side for the left within the text of the program, and it'll evaluate to the same value. Called "referential transparency", this property makes it significantly easier to reason about Haskell programs than other languages.

How will we define our main action? We know that it must be an IO () action, which we want to read the command line args and print some output, producing (), or nothing of value, eventually.

There are two ways to create an IO action (either directly or by calling a function that performs them):

  1. Lift an ordinary value into the IO monad, using the return function.
  2. Combine two existing IO actions.

Since we want to do two things, we'll take the second approach. The built-in action getArgs reads the command-line arguments and passes them along as a list of strings. The built-in function putStrLn takes a string and creates an action that writes this string to the console.

To combine these actions, we use a do-block. A do-block consists of a series of lines, all lined up with the first non-whitespace character after the do. Each line can have one of two forms:

  1. name <- action1
  2. action2

The first form binds the result of action1 to name, to be available in next actions. For example, if the type of action1 is IO [String] (an IO action returning a list of strings, as with getArgs), then name will be bound in all the subsequent actions to the list of strings thus passed along, through the use of "bind" operator >>=. The second form just executes action2, sequencing it with the next line (should there be one) through the >> operator. The bind operator has different semantics for each monad: in the case of the IO monad, it executes the actions sequentially, performing whatever external side-effects that result from actions. Because the semantics of this composition depend upon the particular monad used, you cannot mix actions of different monad types in the same do-block - only IO monad can be used (it's all in the same "pipe").

Of course, these actions may themselves call functions or complicated expressions, passing along their results (either by calling the return function, or some other function that eventually does so). In this example, we first take the first element of the argument list (at index 0, args !! 0), concatenate it onto the end of the string "Hello, " ("Hello, " ++), and finally pass that to putStrLn which creates new IO action, participating in the do-block sequencing.

A new action thus created, which is a combined sequence of actions as described above, is stored in the identifier main of type IO (). The Haskell system notices this definition, and executes the action in it.

Strings are lists of characters in Haskell, so you can use any of the list functions and operators on them. A full table of the standard operators and their precedences follows:

Operator(s) Precedence Associativity Description
. 9 right function composition
!! left list indexing
^, ^^, ** 8 right exponentiation (integer, fractional, and floating-point)
*, / 7 left multiplication, division
+, - 6 left addition, subtraction
: 5 right cons (list construction)
++ right list concatenation
`elem`, `notElem` 4 left list membership
==, /=, <, <=, >=, > left equality, nonequality, and other relation operators
&& 3 right logical and
|| 2 right logical or
>>, >>= 1 left monadic bind ignoring the return value, monadic bind piping value to the next function
=<< right reverse monadic bind (same as above, but arguments reversed)
$ 0 right infix function application (f $ x is the same as f x, but right-associative instead of left)

To compile and run the program, try something like this:

debian:/home/jdtang/haskell_tutorial/code# ghc -o hello_you --make listing2.hs
debian:/home/jdtang/haskell_tutorial/code# ./hello_you Jonathan
Hello, Jonathan

The -o option specifies the name of the executable you want to create, and then you just specify the name of the Haskell source file.

Exercises
  1. Change the program so it reads two arguments from the command line, and prints out a message using both of them
  2. Change the program so it performs a simple arithmetic operation on the two arguments and prints out the result. You can use read to convert a string to a number, and show to convert a number back into a string. Play around with different operations.
  3. getLine is an IO action that reads a line from the console and returns it as a string. Change the program so it prompts for a name, reads the name, and then prints that instead of the command line value


Write Yourself a Scheme in 48 Hours
First Steps Parsing →