Thursday, February 28, 2013

WRITING HASKELL FUNCTIONS

WRITING HASKELL FUNCTIONS

REVISED: Saturday, February 10, 2024




Writing Haskell Functions.

I.  WRITING HASKELL FUNCTIONS

Haskell is sensitive to indentation and spacing.

A. FOLLOW GOOD CODING STYLE

1. Follow the 80 column rule: a line of code must not exceed 80 columns.

2. Avoid too many parentheses by using ($) and (.) function composition.

3. Use {- for large multi-lined comments. -}

4. Use type synonyms to make code readable.

B. DEFINE THE TYPE

We will discuss a Haskell function writing process by using the process to write a function which will provide the product of the elements of a list.

products :: [Int] -> Int     -- Function signature.

The function products takes a list of integers and produces a single integer.

C. ENUMERATE THE CASES

For most types of argument, there are a number of standard cases to consider. For non-negative integers, the standard cases are 0 and n+1, for logical values they are False and True, and so on.

For lists, the standard cases are the empty list and non-empty lists.

products          [] =
products (n : ns) =

D. DEFINE THE SIMPLE CASES

By definition, the product of zero integers is one, because one is the identity for multiplication. Hence it is straightforward to define the empty list case:

products          [] = 1
products (n : ns) =

As in this example, the simple cases often become base cases for recursion.

E. DEFINE THE OTHER CASES

How can we calculate the product of a non-empty list of integers? For this step, it is useful to consider the ingredients that can be used, such as the product function itself, the n and ns arguments, and library functions of relevant types: +, -, *, and so on. In this case, we simply multiply the first integer and the product of the remaining list of integers:

products          [] = 1                           -- Base case.
products (n : ns) = n * products ns   -- Recursive case.

As in this example, the other cases often become recursive cases.


F. GENERALISE AND SIMPLIFY

Once a function has been defined using the above process, it often becomes clear that it can be generalised and simplified. For example, the function products does not depend on the precise kind of numbers to which it is applied, so its type can be generalised from integers to any numeric type:

products :: Num a => [a] -> a     -- Function signature.

In terms of simplification, we see that the pattern of recursion used in products is encapsulated by a library function called foldr.  Using folder, products can be redefined by a single equation:

products = foldr (*) 1

In conclusion, our final definition for product is as follows:

products :: Num a => [a] -> a     -- Function signature.
products = foldr (*) 1

"Copy Paste" the above products function into your text editor and "File, Save As" products.hs to your working directory.

II.  USING GHCI TO VALIDATE HASKELL FUNCTIONS

"Copy Paste" the following module example into your text editor:

module Products where
products :: Num a => [a] -> a     -- Function signature.
products = foldr (*) 1

The example above should be saved as Products.hs.

Open GHCi and load the module Products as shown below:

Prelude> :load Products
[1 of 1] Compiling Products         ( Products.hs, interpreted )
Ok, modules loaded: Products.
Prelude>

Since the function signature for Products shows a list of numbers mapped to a single number; therefore, we have to use a list of arguments when we call the function in GHCi, as shown below:

Prelude> products [1,2,3,4]
24
it :: Num a => a
Prelude>  

III. IDENTITY

True is the identity for && and.
False is the identity for || or.
1 is the identity for * multiplication.
0 is the identity for +  addition.
[] empty list is the identity for ++ append.

IV. REMOVING DUPLICATES

Prelude>  import Data.List
Prelude>

Prelude>  let f = nub [1,1,1,2,2,2,3,3,3]
f :: (Num a, Eq a) => [a]
Prelude>

Prelude>  f
[1,2,3]
it :: (Num a, Eq a) => [a]
Prelude>  

V. FACTORS

"Copy Paste" the following example into your text editor:

factors :: Int -> [Int]
factors n = [x | x <- [1..n], n `mod` x == 0]

prime :: Int -> Bool
prime n = factors n == [1,n]

primes :: Int -> [Int]
primes n = [x | x <- [2..n], prime x]

The example above should be saved as factors.hs to your working directory.

Open GHCi and load factors as shown below:

Prelude> :l factors.hs
[1 of 1] Compiling Main             ( factors.hs, interpreted )
Ok, one module loaded.
*Main> factors 15
[1,3,5,15]
*Main> prime 15
False
*Main> primes 15
[2,3,5,7,11,13]
*Main>  

VI. CONCLUSION
 
In this tutorial, you have learned a process for writing Haskell functions.

VII. REFERENCES

Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.

Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.

Goerzen, J. & O'Sullivan, B. &  Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.

Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.

Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.

Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.