1
2
This example highlights the difference between imperative and functional programming. The imperative programming solution is based on an accumulator (total) and a counter (i); it works by assigning them successive values until the computation is completed. The programmer has to detail, step by step, how the computer is to proceed to obtain the result. Even in such a small example, there are several chances for error and the meaning of the program is not obvious. In contrast the Haskell version consists of applying a function (sum) over a range of integers [1..n]. The meaning is obvious (once one knows the language) and the opportunity for errors are few. 3
4
You will need access to Haskell to do the exercises and homework for the course. If you have a personal computer, Haskell is available for download for Windows, the Mac and Linux; it is also installed on the server: login.cpp.edu, at the prompt use the command GHCi. All those implement the Glasgow Haskell Compiler, which includes an interactive loop where you can type Haskell expressions to be evaluated. You can also type Haskell code in a text file (usually with the.hs extension) and load them. The most effective way to learn Haskell is to use both: write functions in a text file using an editor, load the file (aka module) at the Haskell prompt and then type expressions interactively to test the code. To submit homework for this course I ll ask you to email me the.hs file with your code. 5
When you start Haskell it loads a default environment, called Prelude, which includes a library of commonly used functions. You can find a good description of the Haskell prelude from http://users.dimi.uniud.it/~marco.comini/students/documentation/haskell-prelude- Tour-A4.pdf. At the Prelude prompt you can enter Haskell expressions to be evaluated when you hit the Enter key. Go ahead, start Haskell and enter the expressions above and ensure you get the same result. Most of the operators above should be familiar, with the possible exception of ++, which is the concatenation operator. 6
The simplest haskell declaration is an equation. Using your favorite text editor, go ahead and create a file named digits.hs and enter the following content: one = 1 two = 2 etc. until nine = 9 Remember that Haskell is case sensitive and that the first letter of the names of values must be lowercase. Save the file and at the haskell command prompt type: :load digits.hs If you make a mistake and Haskell fails to load the module you can fix it in the text editor and use the reload command (:r) to retry. Once the file has been successfully loaded it will be considered as the default Main module. At this point the prompt will change to *Main> and you can type commands such as: one two one+two Go ahead and try it. 7
Haskell differentiates between two kinds of entities: Values and Types. Values are the result of expressions. They are first class entities in the language that means that they can be subject of calculations, they can be passed as arguments to functions and can be returned from functions. In Haskell, as in all the common programming languages, numbers, characters, strings, Booleans are values, but, less usual, Functions are also considered as values, hence the term Functional Programming Language. Values can be given a name, and the name of values must start with a lowercase letter and include letters, digits and the single quote character. Haskell is a strongly, statically typed language. That means that all values have a type and the types are calculated statically by the compiler (i.e. without executing the program). Types can be polymorphic, that means that a value can be considered of multiple types at the same time. However all types are part of a hierarchy and each value has exactly one Principal Type. For example the value 1 can be considered as an integer or a real number, but its Principal Type is Num (i.e. it s a numeric type and can be applied numeric operations). We use type variables to express polymorphic types. Type variables start with a lowercase letter to distinguish them from type names. For example, as we ll see later the type expression [a] denotes a list of any type. 8
Haskell supports 6 basic types. Five of them, Bool, Char, Int, Float and Double are the usual types common to most programming languages. The unusual one is Integer which adjusts the number of digits to accommodate any integral number, for example Haskell will happily calculate 1234 to the 567 power (1234^567) and give the result with its 1753 digits; try it. One notable absent is the String type. A string in Haskell is nothing more than a list of characters as we shall see. 9
As mentioned above Haskell is a strongly typed language. Unlike Java and other languages of the C / Algol family explicit typing is largely optional in Haskell. It can be useful, however to specify the type of a value, as a comment or a debugging feature. To specify the type of an element you follow it by a double colon and the type name. Go ahead and try the examples above. In the top group the type of the values is stated and Haskell validates the type before printing the result. In the bottom group Haskell is asked for the type of two values and replies with a type expression. 10
The List type is arguably the second most important and widely used type in Haskell, the first being Function type. A list is a sequence of elements of the same type, separated by comas and surrounded by square brackets. List can be of arbitrary length. The first example above is a list of 5 integers, the second is a list of 3 Bool, the third is a list of 4 Float and the fourth a list of 6 Char; note that the Haskell does not make any difference between a list of Char written using the bracket notation and a list of Char written using the String notation; the String notation is obviously easier to read. The last example shows that lists must be homogeneous: mixing types in a list causes an error. Lists can be of infinite length, for example the list [1..] represents all the integers in order. If you try it at the Prelude prompt you will have to interrupt Haskell otherwise it will continue until it runs out of resources. The type of a list is denoted by the type of the elements of the list surrounded by square brackets. For example [Char] denotes a list of characters and [Integer] denotes a list of integers. As we ve seen before [a] denotes a list of any type, and [[a]] denotes a list of lists of any type. The empty list, i.e. the list that contains no element is denoted [] and is of type [a]. The lists as written above are a convenient notation, however fundamentally lists are constructed using the cons operator, written using a single colon. For example the first list above is the result of the expression: 1 : (2 : (3 : (4 : (5 : [])))) or since : is right-associative 1:2:3:4:5:[] Lists can also be concatenated using the operator ++: [1, 2, 3]++[4] = [1, 2, 3, 4] 11
A Tuple is a finite sequence of elements separated by comas and enclosed in parenthesis. Unlike lists, the elements of a Tuple can be of different types. The type of a tuple includes the type of it s elements. The tuples ('a', 1) and (3, 'c') are not of the same type; try typing :t ('a', 1) and :t (3, 'c'). Tuples can contain Lists and Lists can contain Tuples at any arbitrary length, however, all Tuples within a list must be of the same type as shown in the last example. Note also that while two lists of different length will be of the same type as long as their elements are of the same type (e.g. [1..] and [1] are both of type list of integers, even though the former is infinite while the later has only one element). Tuples can also be polymorphic, for example (a, b) denotes the type of a pair of values any type, while (a, a) denotes a pair where both elements have the same type. 12
As in Object Oriented languages, Classes are the basis for polymorphism, but the comparison does not go much deeper than that. In Haskell a class does not hold any data. This diagram is taken from the Haskell 2010 Report. It indicates the class containment, aka inheritance. Warning: the arrows point in the reverse direction when compared to UML class diagrams. At the top are the EQ and the Show classes. The Eq class denotes types that have an equality operation defined upon them (i.e. the operators equal == and not equal /=). This class includes all the types except Functions and related types such as IO and types that include functions (e.g. lists of functions). The exclusion is based on theoretical grounds: in general the equality between functions is un-decidable. The Show class includes all the types that can be printed, in the standard prelude, the types included in the Show class are the same as the types included in the Eq class, it is however possible to define custom types that are in one class and not the other. Similarly the Num class includes the same types as the Real class, but Complex numbers, for example, belong to the Num class but not the Real class because they are not orderable. For a complete descriptions of the Haskell classes please refer to the Haskell report http://www.haskell.org/onlinereport/index.html. 13
19
Haskell is a Functional Programming Language, so it s no wonder functions are the most important type in Haskell. Functions in Haskell are similar to mathematical functions: they associate elements of a set (the domain) to elements of another set (the range). The type of a function is denoted by an arrow that separates the type of the parameter on the left and the type of the result on the right. The most general type for a function would be a -> b, but no defined functions has this type. Challenge yourself, try to define a function of type a -> b; no, not the identity function, its type is a -> a. 20
Functions in Haskell are considered as values. As such they are first class entities and can be manipulated. - Included in other types like Lists and Tuples. - Passed as argument to other functions. - Returned as result from other functions. In fact the syntax for declaring a figurative constant and a nullary function (a function that takes no argument) is the same. (Remember the definition of one, two, three etc in Lecture 1). 21
To declare a function you simply state one or more equations that specify how the returned value of the function is inferred from the arguments. The function first here is defined by a single equation, multi-equation definitions use a method called pattern matching which we ll see later. When declaring a function it is a good practice to first declare its type. This is not a requirement of the language: the compiler will be fully able to infer the type in all but the rarest cases. Explicitly declaring the type serves three important purposes: - First it forces you to think through your intention - Second it serves as documentation for whoever will read the code, including you - Third it allows the compiler to warn you if your actual function code does not match your intended type, most of the times this will be the indication of a bug. As an example take the definition of the function first above. If you accidentally code first (x, y) = y (as I actually did) the compiler will tell you something is wrong. Go ahead and try it, copy the two lines above to a.hs file and replace the = x by =y and try to load the module, see what happens. 22
To ensure that the required functions are available, it is often necessary to restrict a parameter to types that belong to one or several classes. For example, in order for a function to use the + operation on its argument, that argument needs to be of a type on which the plus operation is defined, in the standard prelude, that would be the Num class. The definition of type constraints is called a context. Multiple constraints can be specified in a single context by surrounding them with parenthesis and separating them with comas. For example the context (Eq a, Num a) =>. states that the type a be in both the Eq and Num classes. 23
There is no special symbol to apply a function to its argument, simply write the function name followed by the argument, separated by white space. For example take the digits.hs file created last time and add the declaration of the inc function from the preceding slide. You can then do the interaction above. It has been argued that because function application is the most common operation in Haskell it has the simplest syntactical form. Function application associates to the left so inc (inc 5) returns 7 while inc inc 5 would return an error since the function inc cannot be applied to itself. Note that while inc cannot be applied to itself due to its type, the function ident defined below can, do you see why? ident x = x. 24
It can be argued that no Haskell function has more than one parameter. In the form 1 above the parameter is a pair of numbers. This resembles the syntax of most common programming languages, but the underpinning semantics is different. The preferred form in Haskell is form 2: Currying. In this form the function add' takes a single argument (the x) and returns a function (call it addx) that takes a single argument (the y) and adds x to it. This is reflected by the type of add' as shown above. And because the -> operator of types associates to the right so the type of add' can also be written a -> a -> a. Currying is more than just a syntactic gimmick; it has profound implications. For example one can now define the function inc as add' 1. Go ahead a add the two definitions above to the file digits.hs, reload it and ask for the type of add' 1 Notice that it is the same as the type of inc. Indeed the equation that defines inc can be replaced by: inc = add' 1 Go head do it and see that is works exactly as inc. Notice that you no longer have to state the parameter of inc: add' 1 is already a function that takes a number and adds 1 to it; inc is just another name for it. 25
The infix notation is the traditional and most intuitive way of writing expressions, as in the usual arithmetic formulae. However, infix operators, do not lend themselves to partial application. To overcome this difficulty Haskell allows infix operators to be used in a prefix notation (the usual function notation). Go ahead and try (+) 1 4 at the prompt and GHCi will happily give the result 5. While the expression 1 + is syntactically incorrect, the expression (+) 1 is correct and denotes a function that adds 1 to its argument (i.e. the now familiar inc function). Haskell goes one step further and allows us to move the 1 inside the parenthesis and hence specify it as either the left or right side operand of +; this process is called sectioning. In the case of the addition, which is commutative, this does not make much of a difference, but for division it does. Namely the functions (1+) and (+1) give the same result while the functions (1/) and (/1) will give different results. Incidentally the minus sign being a prefix as well as an infix the type of the expression (-1) is considered to be Num and not a function. 26
Conversely to sectioning, functions of two parameters can also be used as infix operators, this is sometime convenient to make code more readable. For example the integer division is defined as a function named div. It can be used as a function, as in the first example, or infix as in the second example by surrounding it with back quotes. 27
Haskell implements lazy evaluation, that means that expressions are only evaluated to the extent necessary to produce a result. This allows the use of infinite structures like a list of all the natural integers, noted [1..]. If you typ e [1..] at the GHCi prompt it will respond with a list that goes on and on until forcibly terminated (e.g. Ctrl-c). This again may sound like a gimmick, but it is useful in many circumstances as we ll see during the course. One of the consequences of lazy evaluation is that functions in Haskell are non-strict, that means that a function may return a value even when one or more of its arguments is a non-terminating expression. For example take the infinite sequence of numbers [1..], or the (infinitely) recursive function bot: bot = bot In most languages using either as the parameter to a function will cause the function to run forever. In Haskell the function will only diverge (i.e. non-terminate) if the parameter is completely evaluated. For example, we ve seen the function first :: (a, b) -> a first (x, y) = x First will happily return 1 when applied to (1, bot) but will diverge when applied to (bot, 1). The standard prelude includes a function head that returns the first element of a list and last that returns the last one. The expression first [1..] will return 1 without any problem while last [1..] will send GHCi thinking for a very looooong time. For another example take the function infinity defined as infinity = 1/0 This is a perfectly valid definition and it will not hurt the program until it s value is needed, then an error will occur. It is often necessary to reason about non-termination in program; for that we use the symbol bottom:. Evaluating the function bot above will fail to terminate and is said to return bottom. Finally, it is sometimes possible for a program to detect divergence, such as taking the head of an empty list or dividing by 0. In those cases it is better to generate an error than to let the program run forever. The function error is useful in those cases. It takes a string as a parameter and all good implementations of Haskell will print this message as a diagnostic help. For example when the function head that returns the first element of a list is applied to an empty list, there is no reasonable value for it to return since it does not even know the type of the expected element, such function can be defined as: head :: [a] -> a head [] = error "Taking the head of an empty list" head (x:_) = x 28
Patterns play an important role in functional languages, and in particular for function definitions. Oftentimes the definition of a function involves multiple cases. Recursive functions, for example, typically require the definition of a base case and a recursive case. There are several ways to express those in Haskell, but the easiest to read and hence the most commonly used is pattern matching. The function len in the example above returns the number of elements of a list. It consists of a base case (the empty list) and a recursive case. Patterns are not first class entities; there is a limited set of patterns and they can be used in a limited set of syntactic contexts. Function definition is one of them; we ll see others later in the course. Patterns contain a mixture of structural information, constants and variables. Because all instances could be bound to different values, the same variable name cannot be repeated in a pattern. Patterns are matched against values, for example the pattern on the left side of an equation will be matched against the actual argument(s) when the function is called. Attempting to match a pattern can have one of three results: it may fail in which case the next pattern in the set is attempted; it may succeed, returning a binding for each variable in the pattern; or it may diverge that means non terminate or generate an error. Pattern matching proceeds from top to bottom and left to right. When matching patterns, Haskell only evaluates as much of the expression as necessary to decide success or failure (but may still generate an error in the process). For example the function tk above (it is the equivalent of the standard prelude function take) returns the first n elements of a list, or the entire list if it has fewer elements. Try to apply it as follows and you may be surprised by the behavior. tk 0, [bot] tk 1, [bot] tk 1, [1, bot] 29
The most common types of patterns are: - Literal Matching: in the example above [] matches only the empty list. - Named Variable matching: a named variable matched against any value always succeeds and binds the variable to the value. In the examples above the variable xs is bound to the tail of a non-empty list. - Wildcard or unnamed variable matching always succeeds and no binding is done. In the example, the _ in _:xs is a wildcard and matches the first element of a non-empty list. - Constructor patterns, for example cons (:) is a list constructor, hence the pattern above matches any list built from this constructor, i.e. it has a head and a (possibly empty) tail. (We ll see other constructors when we study custom data types later in the course). - List Patterns have the form [p 1, p 2, p n ] where each p i is itself a pattern. Such a pattern matches a list of exactly n elements and where all the p i patterns successfully match the corresponding element. - Tuple Patterns are similar to List Patterns, but are denoted by parenthesis instead of brackets and apply to tuples instead of lists. Note that the (controversial) n+k pattern (where n is a variable and k is a positive integer literal) has been removed from the language. It used to matche a value v if v >= k, resulting in the binding of n to v - k, it fails otherwise; I mention it here for completeness=: it has been removed from the 2010 standard. 30
Patterns can be useful also in calculations, such is the purpose of the case expression. In fact function definition can be expressed using case. For example the function take from the previous slide can also be defined as follows: take' :: Int -> [a] -> [a] take' n xs = case (n, xs) of { (0, _) -> []; (_, []) -> []; (n, (x:xs)) -> x: take' (n-1) xs} The usual conditional expression is also part of the language. Note that it can be defined using the case statement. Also the else clause is not optional, thus eliminating the need for brackets and the issue of dangling elses. 31
A pattern can analyze the structure and type of an expression, but not its semantics. For example a relationship between the two elements of a pair (such as them being equal or in a certain order) cannot be expressed by a pattern, e.g. the pattern (x, x) is invalid (in part because equality is not always decidable). Guard expressions can be used to overcome this difficulty. A guard is a Boolean expression (and not a pattern). In the first example above x is a pattern that will match any value, the guards will decide which branch to take. You may have noticed that in the definition of tk before nothing would prevent applying take to a negative number and the result would be to return the entire string. Indeed nothing in the pattern can enforce n to be >= 0. Guard solves this problem as in the definition above. 32
Haskell offers an alternative to curly braces and semicolons: layout rules. Most of the time we use program layout to convey scope information (as when we indent the statements inside an if block in Java) without any help form the compiler and hence no guarantee that the layout indeed reflects the program structure. Some IDE alleviate the problem by automatically laying out the program, but this is not fool proof. In Haskell, if you don t use braces then the compiler will enforce that all the lines that belong to the same lexical context start exactly on the same column. So the definition of tk from the previous slide can be laid-out as shown here, without any braces and it is indeed easier to read. Layout is a very nice but sometime treacherous feature; one column off left or right cause weird syntax error messages, but nevertheless it is a largely used and widely appreciated feature. 33
Lambda expressions is an alternate way to define functions. The notation for lambda expression derives from the Lambda Calculus. The lambda calculus was introduced by Alonzo Church in the 1930s as part of an investigation into the foundations of mathematics. The Lambda Calculus is the inspiration and the mathematical foundation for functional programming languages such as Haskell. Lambda expressions are nameless functions. They can be used wherever a named function can be used. They are convenient to reason about programs and give formal meaning to other language constructs, but they are mostly useful when defining functions that return a function; they also avoid having to give a name and defining elsewhere a function that is used only once, thus making the program easier to write and to read. 34
Let expressions are useful whenever a nested set of bindings is required. In this, admittedly artificial, example the names a, b, c and d refer to values bound outside the let, and the programmer has found it convenient to define a value y and a function f to simplify the expression returned by the let, namely (c+a*b)/(a*b) + (d+a*b)/(a*b). Let expressions are particularly useful when a complex expression contains the same sub-expression repeated several times, it allows a name to be given locally to the sub-expression and avoids repetition. Where clauses are useful to create a set of bindings over several guarded equations. In the example above the expression x*x would have to be repeated for each guard and possibly in the returned values, and it would not be obvious to the reader that x is always squared. The where clause allows a name to be given to the value x*x and makes the program easier to read and maintain. 35