Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Beginning Haskell

An introduction to functional programming

David Mertz, Ph.D. (mertz@gnosis.cx), Developer, Gnosis Software
David Mertz
David Mertz has been writing the developerWorks columns Charming Python and XML Matters since 2000. Check out his book Text Processing in Python . For more on David, see his personal Web page. You can contact David at mertz@gnosis.cx.

Summary:  This tutorial is for programmers of imperative languages wanting to learn about functional programming in the language Haskell. If you have programmed in languages such as C, Pascal, Fortran, C++, Java™, Cobol, Ada, Perl, TCL, REXX, JavaScript, Visual Basic, or many others, you have been using an imperative paradigm. This tutorial provides a gentle introduction to the paradigm of functional programming, with specific illustrations in the Haskell 98 language.

Date:  27 Sep 2001
Level:  Introductory PDF:  A4 and Letter (67 KB | 17 pages)Get Adobe® Reader®

Activity:  25842 views
Comments:  

Modules and program structure

Basic syntax

So far in this tutorial, we have seen quite a bit of Haskell code in an informal way. In this final section, we make explicit some of what we have been doing. In fact, Haskell's syntax is extremely intuitive and straightforward. The simplest rule is usually to "write what you mean."

Haskell and literate Haskell

The examples in this tutorial have used the standard Haskell format. In the standard format, comments are indicated with a double dash to their left. All comments in the examples are end-of-line comments, which means that everything following a double dash on a line is a comment. You may also create multi-line comments by enclosing blocks in the pair "{-" and "-}". Standard Haskell files should be named with the .hs extension.

Literate scripting is an alternative format for Haskell source files. In files named with the .lhs extension, all program lines begin with the greater than character. Everything that is not a program line is a comment. This style places an emphasis on program description over program implementation. It looks something like:

Factorial by primitive recursion on decreasing num

>    fac1 :: Int -> Int
>    fac1 n = if n==1 then 1 else (n * fac1 (n-1))

Make an "adder" from an Int

>    mkAdder n = addN where addN m = n+m
>    add7 = mkAdder 7 


The offside rule

Sometimes in Haskell programs, function definitions will span multiple lines and consist of multiple elements. The rule for blocks of elements at the same conceptual level is that they should be indented the same amount. Elements that belong to a higher level element should be indented more. As soon as an outdent occurs, further lines are promoted back up a conceptual level. In practice, it is obvious, and Haskell will almost always complain on errors.

-- Is a function monotonic over Ints up to n?
isMonotonic f n
  = mapping == qsort mapping  -- Is range list the same sorted?
    where                     -- "where" clause is indented below "="
    mapping = map f range     -- "where" definition remain at least as 
    range   = [0..n]          --   indented (more would be OK)

-- Iterate a function application n times
iter n f x
  | n == 0     = x            -- Guards are indented below func name
  | otherwise  = f (iter (n-1) f x)

I find that two spaces is a nice looking indentation for a subelement, but you have a lot of freedom in formatting for readability (just don't outdent within the same level).


Operator and function precedence

Operators in Haskell fall into multiple levels of precedence. Most of these are the same as you would expect from other programming languages. Multiplication takes precedence over addition, and so on (so "2*3+4" is 10, not 14). Haskell's standard documentation can provide the details.

There is, however, one "gotcha" in Haskell precedence where it is easy to make a mistake. Functions take precedence over operators. The result is that the expression "f g 5" means "apply g (and 5) as arguments to f" not "apply the result of (g 5) to f." Most of the time, this sort of error will produce a compiler error message, since, for example, f will require an Int as an argument rather than another function. However, sometimes the situation can be worse than this, and you can write something valid but wrong:

double n = n*2
res1 = double 5^2         -- 'res1' is 100, i.e. (5*2)^2
res2 = double (5^2)       -- 'res2' is 50, i.e. (5^2)*2
res3 = double double 5    -- Causes a compile-time error
res4 = double (double 5)  -- 'res4 is 20, i.e. (5*2)*2

As with other languages, parentheses are extremely useful in disambiguating expressions where you have some doubt about precedence (or just want to document the intention explicitly). Notice, by the way, that parentheses are not used around function arguments in Haskell; but there is no harm in pretending they are, which just creates an extra expression grouping (as in res2 above).


Scope of names

You might think there is a conflict between two points in this tutorial. On the one hand, we have said that names are defined as expressions only once in a program; on the other hand, many of the examples use the same variable names repeatedly. Both points are true, but need to be refined.

Every name is defined exactly once within a given scope. Every function definition defines its own scope, and some constructs within definitions define their own narrower scopes. Fortunately, the "offside rule" that defines subelements also precisely defines variable scoping. A variable (a name, really) can only occur once with a given indentation block. Let's see an example, much like previous ones:

x x y                   -- 'x' as arg is in different scope than func name 
  | y==1      = y*x*z   -- 'y' from arg scope, but 'x' from 'where' scope
  | otherwise = x*x     -- 'x' comes from 'where' scope
  where
  x = 12                -- define 'x' within the guards
  z = 5                 -- define 'z' within the guards
n1 = x 1 2              -- 'n1' is 144 ('x' is the function name)
n2 = x 33 1             -- 'n2' is 60  ('x' is the function name)

Needless to say, the example is unnecessarily confusing. It is worth understanding, however, especially since arguments only have a scope within a particular function definition (and the same names can be used in other function definitions).


Breaking down the problem

One thing you will have noticed is that function definitions in Haskell tend to be extremely short compared to those in other languages. This is partly due to the concise syntax of Haskell, but a greater reason is because of the emphasis in functional programming of breaking down problems into their component parts (rather than just sort of "doing what needs to be done" at each point in an imperative program). This encourages reusability of parts, and allows much better verification that each part really does what it is supposed to do.

The small parts of function definitions may be broken out in several ways. One way is to define a multitude of useful support functions within a source file, and use them as needed. The examples in this tutorial have mostly done this. However, there are also two (equivalent) ways of defining support functions within the narrow scope of a single function definition: the let clause and the where clause. A simple example follows.

f n = n+n*n
f2 n
  = let sq = n*n
    in sq+n
f3 n
  = sq+n
  where sq = n*n
  

The three definitions are equivalent, but f2 and f3 chose to define a (trivial) support function sq within the definition scope.


Importing/exporting

Haskell also supports a module system that allows for larger scale modularity of functions (and also for types, which we have not covered in this introductory tutorial). The two basic elements of module control are specification of imports and specification of exports. The former is done with the import declaration; the latter with the module declaration. Some examples include:

-- declare the current module, and export only the objects listed
module MyNumeric ( isPrime, factorial, primes, sumSquares ) where

import MyStrings    -- import everything MyStrings has to offer
                    -- import only listed functions from MyLists
import MyLists ( quicksort, findMax, satisfice )
                    -- import everything in MyTrees EXCEPT normalize
import MyTrees hiding ( normalize )
                    -- import MyTuples as qualified names, e.g.
                    --   three = MyTuples.third (1,2,3,4,5,6)
import qualified MyTuples

You can see that Haskell provides considerable, fine-grained control of where function definitions are visible to other functions. This module system helps build large-scale componentized systems.

5 of 7 | Previous | Next

Comments



Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=132894
TutorialTitle=Beginning Haskell
publish-date=09272001
author1-email=mertz@gnosis.cx
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.