Entering our first examples, we are happy not to encounter any surprises. We can print our favorite message and evaluate arithmetic expressions as if we were using Python.
Prelude> "Hello World" "Hello World" Prelude> print "Hello World" "Hello World"
If we switch on the type printing in the options menu (or using the +t option with the command line interpreter), Hugs responds like the ML systems with the value and the type of an expression.
Prelude> "Hello World" "Hello World" :: String Prelude> 1.5 1.5 :: Double
How does Hugs perform as a calculator? Since Haskell belongs to the "syntactically rich" function languages, it supports arithmetic operators that behave like they are supposed to.
Prelude> 3 + 4 * 5 23 :: Integer Prelude> 1.5 - 2 -0.5 :: Double Prelude> mod 5 3 2 :: Integer Prelude> mod 3 3 + 1 1 :: Integer
Note that, in contrast to ML (and Ocaml with its "dotted" operators such as +.), we can mix integers and floating point numbers in one expression. Haskell allows operators and functions to be overloaded for different argument types.
Functions such as mod are applied by just putting them in front of their arguments without any special syntax for the argument list. In fact, Haskell does not know the concept of multiple arguments. Every Haskell function is called with exactly one argument. The expression mod 5 3 actually consists of two function calls. First the mod function is applied to the single argument 5 resulting in a new function which is then applied to the single argument 3. The expression is therefore equivalent to (mod 5) 3.
Prelude> (mod 5) 2 1 :: Integer
The infix notation for operators is just syntactic sugar, and we can switch back and forth between operator and function syntax by enclosing the operator in parentheses or the function in back quotes.
Prelude> (+) 4 5 9 :: Integer Prelude> 5 `mod` 2 1 :: Integer
Being able to treat operators as functions makes it easy to use them as arguments to higher order functions as we will see below.
If we are interested in just the type of an expression (and not its value), we can discover it with the :type command. This is of particular interest for functions, since they do not know how to display themselves.
Prelude> :type "Hello" "Hello" :: String Prelude> :type mod mod :: Integral a => a -> a -> a
The first part Integral a => of the function type tells us that a is a type which behaves like an integer. In other words, it must belong to the type class Integer. We will explain these concepts later in detail.
The right hand side a -> a -> a confirms that functions have one argument only. The type expression has to be read as a -> (a -> a) and tells us that mod is a function with an argument of type a returning a function which again takes an argument of type a and returns a value of type a. If we apply mod to the single argument 5, we obtain a new function of type Integral a => a -> a.
Prelude> :type mod 5 (5 `mod`) :: Integral a => a -> a Prelude> (mod 5) 2 1
Note that the function application is left associative with f x y being equivalent to (f x) y, whereas the arrow of the function types is right associative with a -> b -> c being equivalent to a -> (b -> c)).
As a functional language, Haskell has conditional expressions (rather than the conditional statements we are used to from imperative languages). Here is the if-then-else expression:
Prelude> if 1 < 2 then "ok" else "not ok" "ok"
Since the result of the expression has to be well defined (and typed), the else part is required and both, then and else part have to be of the same type.
The other conditional expression if the case contruction. We can not show this directly on the Hugs command line, because Hugs does not allow multi-line commands. Instead, we have to put the definitions in a file and load this file using the :load command. We create a file called test.hs and enter the following code:
x = 5 y = case (x) of 1 -> "one" 2 -> "two" _ -> "more"
Each Haskell file consists of a list of definitions, in our case just two. We first bind the integer 5 to the symbol x. The next definition contains the case expression we were interested in. Depending on the value of x, we bind y to an appropriate string.
The case expression consists of the expression to be checked between the two keywords case and of, followed by the list of alternatives, each mapping a pattern on the left hand side to an expression on the right hand side of the array operator. A pattern can be a single value such as 1 or 2, or a variable such as the dummy variable _ which is typically used for the default case. As we will see later, a pattern can also include a constructor such as a tuple or the list constructor :.
How does Haskell know that the four lines of the case construction belong together? Similar to Python, Haskell uses the layout to decide where a definition ends. Haskell's offside rule states that a new definition starts at the same indentation level or to the left of the start of the definition.
To use these definitions, we have to load the file from the Hugs interpreter using the :load command (all Hugs commands start with a colon).
Prelude> :load test.hs Reading file "test.hs": Hugs session for: C:\Program Files\Hugs98\lib\Prelude.hs test.hs Main> y "more"
Note that the prompt has now changed to Main> showing the name of the default module (since we have not specified any module in our file).
We can also separate the alternatives of the case with semicolons in a single line, but this style is much less readable and therefore hardly ever used.
Prelude> case (3) of 1 -> "one"; 2 -> "two"; _ -> "more" "more" :: [Char]
Before we dive into types and type classes, we will define a few simple functions of our own. Again, we can not do this right away in the Hugs shell, but have to put the definitions our test file test.hs.
times2 x = 2*x fac 0 = 1 fac n = n * fac(n-1)
We then reload the file and test the functions:
Main> :reload ... Main> times2 55 110 :: Integer Main> fac 5 120 :: Integer
Looking at the code, this is probably the shortest form possible for defining the times2 function. It also explains why we can't define the function on the command line. There is no special keyword such as def or val introducing the definition, just the plain equation.
The fac functions shows Haskell's minimal syntax for pattern matching and recursive functions. We just write down the defining equations for the different patterns. A pattern can be a single value (such as 0 in our example), a variable (such as n), or some constructor expressions for a tuple, list, or user defined data type (as we will see further down).
Often patterns are not enough to distinguish the different cases of a function definition. In this case, we can define the function using so-called guards which allow for arbitrary conditions depending on the arguments. As an example, we define the sign function in our test module.
sign x | x < 0 = -1 | x == 0 = 0 | x > 0 = 1
After changing the module file, we can reload the module from the shell (without leaving the interpreter) using the reload command.
Main> :reload ... Main> sign -55 -1 :: Integer Test: sign 1.5 1 :: Integer
Of course, we could have also used the if expression to achieve the same effect in a less readable manner.
sign1 x = if x < 0 then -1 else if x > 0 then 1 else 0
We can't define named functions on the command line, but Haskell does understand lambda expressions:
Prelude> (\x -> 2*x) 4 8 :: Integer
Here, the backslash is supposed to look like a lambda, and the definition uses an arrow instead of the equal sign in the normal function definition above.
Higher order functions (functions taking function arguments and possibly returning new functions) following Haskell's mathematical style: just write down the defining equation. Here is the definition of the composition of two function (one function applied to the result of the other):
compose f g x = f (g x)
After entering this definition in our test module (test.hs), we can reload the module and apply the new higher order function.
Main> :reload ... Main> :type compose compose :: (a -> b) -> (c -> a) -> c -> b Main> compose (5+) (10+) 5 20 :: Integer
Note that Haskell automatically derives the most general type for the compose function. Alternatively, we could have used the lambda syntax to clearly indicate that we are returning a new function.
compose f g = \x -> f (g x)
It probably does not come as a surprise that the composition is part of the standard library in form of the . operator.
Main> :type (.) (.) :: (a -> b) -> (c -> a) -> c -> b Main> ((5+) . (10+)) 5 20 :: Integer
You may have got the impression already that Haskell is a "mathematical" programming language. It shouldn't be surprising that tuples are a natural and common type (or better type constructor) in Haskell.
Main> (1, 1.5) (1,1.5) :: (Integer,Double) Main> fst (4, 5.5) 4 :: Integer Main> snd (4, 5.5) 5.5 :: Double
We construct a tuple following the mathematical notation (like in Python). The two functions fst and snd give us the first and second element of the tuple, respectively. Note that these function calls look as if we apply a function to multiple arguments, but we are in fact applying the functions to single arguments of type tuple.
Like infix operators, the tuple notation is just syntactic sugar for the application of the "tuple functions" (,), (,,), and so forth.
Prelude> :type (,) (,) :: a -> b -> (a,b) Prelude> :type (,,) (,,) :: a -> b -> c -> (a,b,c) Prelude> (,) 1.5 2 (1.5,2) Prelude> (,,) 1.5 2 "a" (1.5,2,"a")
List literals look like in Python, but must contain a single type of elements.
Main> [1, 2, 3] [1,2,3] :: [Integer] Main> [1, "bla"] ERROR - Illegal Haskell 98 class constraint in inferred type *** Expression : [1,"bla"] *** Type : Num [Char] => [[Char]]
Note that the notation for list types reflects list literals by enclosing the element type in square brackets and that strings are just lists of characters, that is, have type [Char].
Haskell uses a single colon as the list constructor (cons in Lisp, double colon in ML) and the double plus ++ to concatenate two lists. We can access individual elements with the !! operator.
Prelude> 1:2:3:[] [1,2,3] :: [Integer] Prelude> [1, 2, 3] ++ [4, 5, 6] [1,2,3,4,5,6] :: [Integer] Prelude> "Hello " ++ "World" "Hello World" :: [Char] Prelude> [1,2] !! 1 2 Prelude> [1,2] !! 0 1
As a generalization of the familiar head and tail functions, Haskell offers a symmetric set of functions for accessing the beginning or end of a list: head and last give the first and last element, respectively, tail and init all but the first or last element, and take and drop give you the first so many elements or the rest of the list.
Prelude> head [1, 2, 3] 1 Prelude> last [1, 2, 3] 3 Prelude> init [1, 2, 3] [1,2] Prelude> tail [1, 2, 3] [2,3] Prelude> take 2 [1, 2, 3, 4] [1,2] Prelude> drop 2 [1, 2, 3, 4] [3,4]
We can also check if a value is contained in a list (or not), reverse it, compute the sum or product, combine two lists into a list of pairs (zip) or vice versa (unzip). However you would like to process a list, Haskell has most likely a function in the Prelude which does the right thing.
Prelude> elem 5 [1, 2, 3] False :: Bool Prelude> elem 2 [1, 2, 3] True :: Bool Prelude> reverse [1, 2, 3] [3,2,1] :: [Integer] Prelude> sum [1, 2, 3] 6 :: Integer Prelude> product [1, 2, 3, 4] 24 :: Integer Prelude> zip [1, 2, 3] ["a", "b"] [(1,"a"),(2,"b")] :: [(Integer,[Char])] Prelude> unzip [(1,"a"),(2,"b")] ([1,2],["a","b"]) :: ([Integer],[[Char]])
Besides these "normal" list functions, a functional language such as Haskell has of course a number of higher order functions operating on the list as a whole.
Prelude> map (\x -> 2*x) [1, 2, 3] [2,4,6] :: [Integer] Prelude> foldr (+) 10 [1, 2, 3] 16 :: Integer Prelude> filter odd [1, 2, 3, 4, 5] [1,3,5] :: [Integer]
And guess where Python's list comprehensions come from? In Haskell, they look just like the mathematical definition. The "element of" sign is <- (somewhat looking like an epsilon).
Prelude> [2*x | x <- [1,2,3] ] [2,4,6] :: [Integer] Prelude> [2*x | x <- [1,2,3], odd x ] [2,6] :: [Integer] Prelude> [2*x + y | x <- [1,2,3], odd x, y <- [10,20] ] [12,22,16,26] :: [Integer]
On the left hand side of the bar we have an expression whose results are put into the generated list. On the right hand side of the bar we can put any combination of generators (such as x <- [1,2,3]) and tests (boolean expressions such as odd x). Haskell iterates through the generators, checks the tests, and, in case the tests are true, places the result of the expression in the list.
Haskell also offers a number of means to generate lists. In the simplest case, we can use ellipses to generate a sequence of consecutive integers.
Prelude> [1..5] [1,2,3,4,5] :: [Integer] Prelude> [100..110] [100,101,102,103,104,105,106,107,108,109,110] :: [Integer]
We can also vary the step size and generate decreasing numbers.
Prelude> [1,4..20] [1,4,7,10,13,16,19] :: [Integer] Prelude> [10,8..0] [10,8,6,4,2,0] :: [Integer]
We can even generate infinite sequences. Since Haskell's Integer type is unlimit (ok, limited only by the memory constraints), these sequences go on forever unless we interrupt the program by pressing the stop button or Ctrl-C.
Prelude> [0,10..] [0,10,20,30,40,50,60,70,80,90,100,110,120,130,{Interrupted!}
Studying the Prelude, we discover more functions generating sequences such as replicate (repeating the same value a specified number of times) and iterate (applying a function over and over again). The iterate function again generates an infinite sequence. We can use the take function to see the first so many elements without having to interrupt the program.
Prelude> replicate 10 "a" ["a","a","a","a","a","a","a","a","a","a"] :: [[Char]] Prelude> take 10 (iterate (2*) 1) [1,2,4,8,16,32,64,128,256,512] :: [Integer]
The infinite lists touch another fundamental property of Haskell: lazy evaluation. Each expression is evaluate only if it is needed and only once if it is needed multiple times. In the case of infinite lists, the list is not constructed before it can be used (this would take a little bit too long). Instead, the next value is generated exactly when it is needed.
In contrast to ML, we can apply the functions times2 and sign defined above to integers as well as floating point numbers.
Main> times2 5.5 11.0 :: Double
This makes us curious about the type of times2. In ML, the compiler would have inferred from the integer constant and the multiplication that the function is an integer function. Haskell is smarter and uses less restrictive types.
Main> :type times2 times2 :: Num a => a -> a Main> :type (*) (*) :: Num a => a -> a -> a
Our new function is of type Num a => a -> a. What is this supposed to mean? The right hand side a -> a tell us that it is a function mapping a value of some type a to the same type. The left hand side is a type restriction. It tells us that a has to be a numeric type, that is, a type of class Num. Similarly, the type of the multiplication is a (curried) function with two arguments, both of the same numeric type and resulting in the same numeric type. How is Num defined? The :info command tells us all about it.
Main> :info Num -- type class infixl 6 + infixl 6 - infixl 7 * class (Eq a, Show a) => Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a fromInt :: Int -> a -- instances: instance Num Int instance Num Integer instance Num Float instance Num Double instance Integral a => Num (Ratio a)
We see that a numeric type has to support the basic arithmetic operators and conversion functions from integers. The command also shows the currently available types of class Num. There is more information hidden in the class statement class (Eq a, Show a). The class Num is derived from Eq. In plain words, each numeric type must also be an equality type.
Main> :info Eq -- type class infix 4 == infix 4 /= class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool -- instances: instance Eq () instance Eq Char ...
Checking the class definition, we find out that an equality type has to support - surprise, surprise - the equality and inequality operators.
Haskell is not restricted to the build-in types and classes like Integer and Num. They are nothing special and we can define our own types and classes and use them as if they were part of the standard modules. Let's start with our own types. First, we can define shortcuts for existing types. This doesn't give us anything new besides a more readable way to deal with complex type constructors.
type ShopItem = (String, Float) type Basket = [ShopItem] shopSum :: Basket -> Float shopSum [] = 0.0 shopSum ((_,i):xs) = i + shopSum xs Main> :reload ... Main> shopSum2 [("apple", 1.5), ("pear", 2.5)] 4.0 :: Float
We could have defined the function directly using the type expression [(String, Float)] in the type declaration for shopSum, or, even shorter, without any type declaration.
shopSum2 [] = 0.0 shopSum2 ((_,i):xs) = i + shopSum xs
Having seen ML, user defined types in Haskell are nothing new. As with function definitions, we can't type them interactively, but have to put the definitions in a module. We'll use our test module again. We see the familiar data constructors, tuple types, union types, and recursive types.
Haskell touts itself as a pure functional language, and for now we have restricted ourselves to pure definitions without any side effects. In the real world, however, we would like to read a file or print a message among other useful actions causing side effects.
How can we introduce imperative features without compromising the functional core of the language? The basics idea is to consider actions as values of special types. These "action values" can be combined in a controlled fashion to form larger actions. The simplest example takes us back to our favorite message.
Prelude> print "Hello World" "Hello World" :: IO ()
This looks like a pure statement taken from an imperative language (it works in Perl and Python, for example). The only surprising thing is the type IO () of the expression. The expression print "Hello World" is a I/O action. The interpreter happens to treat actions by executing them resulting in the output "Hello World" (as the side effect of the action).
The situation get clearer when looking at the type of the function print.
Prelude> :type print print :: Show a => a -> IO ()
The function print takes a showable value (that is, of type class Show) and returns an I/O action of type IO (). IO is a built-in type constructor for input and output actions. The type IO a is an I/O action returning a value of type a. The function getLine, for example, is of type IO String, since is corresponds to an action resulting in a string.
Prelude> :type getLine getLine :: IO String Prelude> getLine 1234 "1234" :: IO String
Since the print action does not return anything, it is of type IO () with the unit type ().
We can work with actions like with any other value, for example, store them in collections or pass them to functions.
Prelude> [print "Hello", print "World"] [<<IO action>>,<<IO action>>] :: [IO ()] Prelude> [print "Hello", print "World"] !! 1 "World" :: IO () Prelude> (print "Hello", getLine) (<<IO action>>,<<IO action>>) :: (IO (),IO String) Prelude> snd (print "Hello", getLine) asdf "asdf" :: IO String
A first simple way to combine actions is an if expression:
Prelude> if 1 < 2 then print "Hello" else print "World" "Hello" :: IO ()
Another natural operation for actions is sequencing (which is at the core of imperative programming). Haskell's do construct lets us combine multiple actions so that the resulting action executes the combined actions in sequence.
Prelude> do print "Hello"; print "World" "Hello" "World" :: IO ()
Here is a more elaborate example using do to recursively perform an action multiple times.
times n action = if n <= 1 then action else do action times (n-1) action
This way, we can easily print our message many times.
Main> times 3 (print "Hello World") "Hello World" "Hello World" "Hello World" :: IO ()
What is missing until now is a link between I/O actions type IO a) and the underlying values of type a. Within a do block, we can bind the result of an action to a symbol with the <-. Here is an action which echos a line read from standard input.
echo = do line <- getLine print line
Superficially it looks like an assignment, but it is just giving the result of an action (here getLine) a name which can be used in the next actions (here print) of the do construct.
Main> echo asdf "asdf" :: IO ()
Note that the <- is restricted to do blocks. This way, the results of actions can not spoil the functional program.
Going in the opposite directory, the return function turns any value into an I/O action which does nothing (no side effects) but return the original value.
Prelude> return "Hello" :: IO String "Hello" :: IO String
This does not accomplish much by itself, but is sometimes needed to manipulate values within actions. As an example, we have a look at an extended echo program which echos line after line until the end of file is reached. First, we need a while loop which runs an action until a test (of type IO Bool) becomes true.
while test action = do res <- test if res then do action while test action else return ()
Note the return () expression which is the I/O action which does absolutely nothing, but is needed as the else expression.
Next, we apply the while loop to the end-of-file condition. The built-in action isEOF is of type IO Bool, but unfortunately returns the opposite of the test we need in the while loop. Therefore, we have to create a new test action which gets the result and returns the negated value.
import IO echo = while (do result <- isEOF return (not result)) (do line <- getLine print line)
Alternatively, we can first define a new function ioNot which negates I/O actions
ioNot test = do result <- test return (not result)
and then use it in the condition of the while loop.
echo2 = while (ioNot isEOF) (do line <- getLine putStrLn line)
So, what actually is this type constructor IO? What makes it special so that we can use it with sequencing (do), naming of results (<-) and return constructs? Checking the type of the return function we see that it is defined for any type a b where the type constructor a is a Monad.
Main> :type return return :: Monad a => b -> a b
Similarly, Haskell's type inference determines that the type of our times function relies on a monad.
Main> :type times times :: (Ord a, Num a, Monad b) => a -> b c -> b c
The number of times we want to iterate must be an ordered number (type classes Ord and Num), and the action to be repeated is of type b c where the type constructor b must be a monad again.
A monad? What is this? No worries, we will not get into its origin in category theory (also known as "abstract nonsense"). In practice, it is enough to know that monads generalize the concept of I/O actions we have seen above to all the "action types" for which the "action operations" such as sequencing and return make sense.
[1] | If may have trouble running this program with Hugs, since the Hugs version I've used did not implement the isEOF action yet. |