• No results found

Haskell A purely functional programming language

language

Haskell, is a purely functional programming language. It has been around and developed since 1990, and has a large and growing user base. It relies heavily on pattern matching and currying, allowing for a more natural, mathematically based, way of defining functions. There are many compilers and interpreters available for Haskell, although GHC (The Glasgow Haskell Compiler) is fast becoming the most commonly used because of it’s large library of extensions. The compilers, along with the Haskell language definition [Jon03], can be found on the Haskell homepage: www.haskell.org. The Haskell homepage also contains descriptions of all the standard libraries, along with tutorials and much more information on the language itself. There are also a number of introductory textbooks available, e.g. [Hut07].

In this section we shall give a brief introduction to Haskell, starting with an introductory example that is often given as a first programming exercise in func- tional programming languages. Namely, defining a function that calculates the factorial of the given input. Firstly, it is useful (although not always necessary) to give the type of the function you wish to define. In this case, the factorial func- tion is a function from an integer to another integer (Int being a 32 bit integer in Haskell).

factorial :: Int → Int

We can now make use of pattern matching to split the function definition into its constituent parts, firstly the base case for when the input is zero (the output is one), and secondly the recursive case for when the input (n) is greater than zero (the output is n multiplied by the factorial of n-1). In Haskell we can define this as:

factorial 0 = 1

factorial n = n∗ factorial (n − 1)

The factorial function is a nice introductory example as it shows off some important techniques that are extensively used in Haskell. The use of pattern matching allows function definitions to be split depending on the input. When evaluating a function that is defined using pattern matching, Haskell will look sequentially through each definition until the input matches the given pattern, in the case of the factorial function the second line will be evaluated unless the input is 0. The other major technique employed by the definition of the factorial function above is its use of recursion. That is the function is able to call itself. Mostly, the recursive call will need to be over a smaller input than the original call, although it is the job of the programmer to ensure this is the case (to prevent non-terminating programs). In this example it is quite clear that the recursive call is over a smaller argument, as n − 1 is smaller than n. However, the definition of smaller isn’t necessarily as clear cut as in this case, and indeed the function definition as it stands would be non-terminating for a negative input.

Another data-type that is extensively used in Haskell is the list datatype. A list in Haskell is a possibly infinite list of elements with the same type. For example a list of integers would have the type [Int ], and we can now write functions acting on these lists. For example we could write a function that reverses a list of integers, starting again by defining the type of the function, and then using pattern matching over the two list patterns, [ ] which matches with the empty list, and (x : xs) which matches with a non-empty list with head element x and a (possibly empty) tail xs.

reverseList :: [Int ]→ [Int ] reverseList [ ] = [ ]

reverseList (x : xs) = reverseList xs ++ [x ]

From this we can see that the reverse of an empty list is an empty list, and the reverse of a non-empty list is the reverse of the tail of the list concatenated onto the front of the singleton list containing the head element. If we look more closely at the definition of the reverseList function above, we’ll also notice that we haven’t needed to look at the values of the individual elements of the list. Indeed, this is often the case with list operations, and Haskell provides us with a more generic way of defining operations on lists so that we don’t have to rewrite the function for every possible type of list. We can define a more generic list reverse function whereby the type a is any arbitrary type.

reverseList′ :: [a ]→ [a ]

reverseList′ [ ] = [ ]

reverseList′ (x : xs) = reverseListxs ++ [x ]

This reverseList′ function is able to reverse a list containing elements of any type,

and the type checker is able to infer at runtime which specific instance for a is being used. However, it is important to remember that if we need to look at the value of any of the elements of this list this generic definition may no longer be correct. For example, if we were writing a function to sort a list we would need to know that the elements had some sort of ordering. We shall look at how Haskell

uses type-classes to deal with this separate kind of generic programming in section 4.4, but for now we shall look at the specific example of sorting a list of integers. As lists are used quite extensively in Haskell, we are provided with a form of list comprehension. This simple sort function for a list of integers can be thought of as creating a list of elements less than the head of the original list, and a list of elements greater than the head of the original list, which are both then sorted themselves before being concatenated back onto either side of the head of the original list. We make use of the where keyword to give variable names to the two parts of the list we are sorting (although they are both still immutable data).

sortList :: [Int ]→ [Int ] sortList [ ] = [ ]

sortList (x : xs) = ltx ++ [x ] ++ gtx

whereltx = sortList [y | y ← xs, y 6 x ] gtx = sortList [y | y ← xs, y > x ]

The code [y | y ← xs, y 6 x ] can be read as creating a list of all elements y such that y comes from the list xs and y is less than or equal to the element x . Similarly for gtx ([y | y ← xs, y > x ]) we have the list of all elements y such that y comes from the list xs, and y is greater than the element x . The recursive calls to the sortList function will sort these two sublists before they are recombined with the original head element x . List comprehension can be a very useful tool when creating functions over lists in Haskell. The sortList function is in fact an implementation of the Quicksort algorithm. As previously mentioned we shall look at how a more generic list sorting algorithm can be defined using type-classes in section 4.4.

All the functions defined so far have been pretty simple introductory examples to try and give the reader a taste for how programs are written in Haskell. The next few sections shall introduce more programming constructs which are used later in this thesis for the implementation of the Quantum IO Monad, with section 4.6 having some more relevant examples to compare with the quantum computations

written using the QIO Monad in chapter 6. One last example I shall give here is to introduce the idea of an accumulator function. Accumulator functions can often be used to implement more efficient functions acting on lists, and the simplest example of an accumulator function is to re-write our list reversing algorithm using one.

reverseListAccumulator :: [a ]→ [a ] → [a ] reverseListAccumulator xs [ ] = xs

reverseListAccumulator xs (y : ys) = reverseListAccumulator (y : xs) ys reverseList′′:: [a ]→ [a ]

reverseList′′ = reverseListAccumulator [ ]

Accumulators are a form of higher order function that use an extra data-structure to enable the result to be accumulated rather than constructed explicitly. As we can see, the accumulator function itself takes two lists as arguments, and in essence shifts the head of one list onto the head of the other list creating a stack like structure whereby every time the head is popped off the top of one list, it is then pushed onto the top of the other. It is this process that reverses the list when the function is called with the empty list as its first argument. The reason this function is more efficient than the previous implementation of the reverseList′

function comes from the way that the concatenation operation on lists is defined. In the accumulator example above, the original input list is only traversed once, whereas in the original implementation the list is traversed for every call of the concatenation function (++), which is called on each recursive call of the overall function.