Tuesday, February 12, 2013

HASKELL USER-DEFINED FUNCTIONS

HASKELL USER-DEFINED FUNCTIONS

REVISED: Wednesday, February 7, 2024




Haskell user defined functions.

I.  HASKELL USER-DEFINED FUNCTIONS

A function in Haskell is really a function, just as mathematicians intended it to be. A function is called higher-order if it takes a function as an argument or returns a function as a result. Functions are just another kind of data; therefore, it is legal to return functions as the return value from other functions. In a functional language everything, even a program, is a function. Arity is the number of arguments a function can take.

The syntax of a function definition is function name followed by argument names, then an equal sign, and an expression. In Haskell function and variable names always start with a lowercase letter. For example:

f(x) = x²

Using the Haskell Prelude interpreter the above function becomes:

Prelude>  let f x = x ^ 2  -- Function name is f, argument is x, expression is x ^ 2.
Prelude>

Prelude>  f 4  -- Function application; i.e., calling the function (4 * 4).
16
Prelude>

Prelude>  f 3  -- Function name, space, arguments calls the function (3 * 3).
9
Prelude>  

Prelude>  :type f  -- To obtain the function type; i.e., signature.
f :: Integral b => b -> b  -- Function signature.
Prelude>  

A.  COMPOSITION OR COMBINATION OPERATOR

Applying a function to the result of another function is called function composition. The composition or combination operator (.) takes two functions and combines them. The (.) function is right associative, so we work from right to left. Using the combination operator (.) we create a function which takes the arguments of the first, and returns the return-value of the second.

Prelude> :type (.)     -- :t is the same as :type
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude>

As shown above, (.) takes two functions as its arguments. The first argument (b -> c) is a function that takes b and returns c. The second argument (a -> b) is a function that takes a and returns b.

First example:

Prelude> let muladd = addx . mulx
addmul :: Num c => c -> c
Prelude>

Computing right to left: first mulx is computed; second addx is computed using the results from mulx. Therefore, compute addx after computing mulx.

Right to left: first mulx = 6 * 6 = 36; second addx = 36 + 36 = 72. 

Prelude> let mulx x = x * x             -- Second function (a -> b) computed first, will pass its results to the first function.
mulx :: Num a => a -> a
Prelude>

Prelude> mulx 6                               -- 36 = (6 * 6) when computed alone.
36
it :: Num a => a
Prelude>

Prelude> let addx x = x + x              -- First function (b -> c) computed last.
addx :: Num a => a -> a
Prelude>  

Prelude> addx 36                             -- 72 = (36 + 36) when computed alone.
72
it :: Num a => a
Prelude>

Prelude>  muladd 6
72
it :: Num c => c
Prelude>  

Second example:

Prelude> let addmul = mulx . addx
muladd :: Num c => c -> c
Prelude>

Computing right to left: first addx is computed; second mulx is computed using the results from addx. Therefore, compute mulx after computing addx.

Right to left: first addx = 6 + 6 = 12; second mulx = 12 * 12 = 144. 

Prelude> add
mul 6
144
it :: Num c => c
Prelude>

Third example:

Prelude> filter ((<=9).(*3)) [1,2,3,4,5,6,7,8,9]
[1,2,3]

it :: (Ord b, Num b) => [b] Prelude>

Right to left: Filter out original list elements "after" first multiplying each list element by three; and then testing each result to see if it is (<= 9).

(1*3)(<=9)True
(2*3)(<=9)True
(3*3)(<=9)True
(4*3)(<=9)False
(5*3)(<=9)False
(6*3)(<=9)False
(7*3)(<=9)False
(8*3)(<=9)False
(9*3)(<=9)False


B.  INDENTATION

There is very little redundancy in Haskell so almost everything, including whitespace, has meaning.

Operator whitespace has the highest precedence and binds to the left.

Haskell recognizes blocks by indentation and newlines as separators.

Indentation matters. Top level declarations start in the first column. A declaration can be broken at any place, and continued in the next line, provided that the indent is larger than the indent of the previous line.

C.  FUNCTION NAMES

Function names can not begin with uppercase letters; and calling a function with the same arguments always returns the same value. In Haskell, there is a "main function" and every object has a type. The type of the main function is IO ( ).

Type constructors are the only functions starting with a capital letter, and the only functions that can appear on the left-hand side of an expression.

D.  FUNCTION EXAMPLE

Length is a function that counts the number of elements in a list. The :: separates the name of the function from the type definitions. The characters -> separate the different type definitions. The last type definition is the result, output, of the function while the others are parameters, input.

-- length function signature
length               :: [a] -> Integer            -- length takes a list of a, returns an Integer.
length         [ ]  = 0                                  -- Base case, empty list.
length (x : xs)  = 1 + length xs              -- Recursive case.

This definition is almost self-explanatory. length is a function that takes a list containing values of type a and gives back a value of type Integer. We can read the equations as saying: "The length of the empty list is 0, and the length of a list whose first element is x and remainder is xs is 1 plus the length of xs." Note the naming convention used here; xs is the plural of x, and should be read that way. Examples of the above length function are as follows:

Prelude> length [1, 2, 3, 4, 5]                             -- [ ] pronounced "list of" when not an empty list.
5
it :: Int
Prelude>

Prelude> length ["1", "2", "3", "4", "5"]      -- "List of" Characters.
5
it :: Int
Prelude>

Prelude> length [("1", "2"), ("3", "4")]         -- "List of" Tuples of Characters.
2
it :: Int
Prelude>

Prelude> length [["1", "2"], ["3", "4"]]         -- "List of" Character lists.
2
it :: Int
Prelude>

Prelude> length [[1, 2], [3, 4], [5, 6]]                -- "List of" Integer lists.
3
it :: Int
Prelude>

E.  CONS OPERATOR

The : operator, also called the cons operator, takes a number and a list of numbers or a character and a list of characters. The value to the left of the : is placed as the first element of the list. The process of adding an element is called "consing".

Prelude> :type (:)
(:) :: a -> [a] -> [a]
Prelude>

The cons operator : takes a value of type a and another value of type [a] list of as, and produces another value of type [a] list of as.

The cons operator associates to the right. For example, [1,2,3] is an abbreviation for 1 : (2 : (3 : [ ])).

Cons patterns must be parenthesised when defining functions, because function application has higher priority than all other operators.

F.  POLYMORPHIC FUNCTIONS

Polymorphism means that a single function can be applied to a variety of argument types. The length function is an example of a polymorphic function. It can be applied to a list containing elements of any type, for example [Integer], [Char], or [[Integer]]the caller gets to pick the type. 

G.  APPLY ($) OPERATOR

In Haskell, function application is left associative. When an argument to a function is a result of another function, you have to use parentheses.

For example:

a b (c d)

means the function a acting on two arguments, b and (c d), which itself is an application of function c to d.

The parentheses can be removed by using $ operator.

a b $ c d 

The $ function is also an operator for function application. Function application with $ is right-associative. Using $ we do not have to write as many parentheses. When a $ is encountered, the expression on its right is applied as the parameter to the function on its left. $ means that function application can be treated just like another function. For example, we can map function application over a list of functions.  

Prelude> :type ($)
($) :: (a -> b) -> a -> b
Prelude>

$ binds to the right and has the lowest precedence of any operator. The $ operator is used to leave out the closing parentheses in a Haskell statement. When you see a $ mentally replace it with a left open parentheses ( and mentally place the right closing parentheses ) at the end of the statement. Neither a $ nor a closing parentheses ) is coded at the end of the statement.

H.  TUPLES

We often use tuples to return multiple values from a function. 

A tuple is a fixed-size collection of values, where each value can have a different type. This distinguishes them from a list, which can have any length, but whose elements must all have the same type. Haskell tuples are not immutable lists.

I.  REWRITE HASKELL FUNCTIONS

One way to practice writing functions is to write your own version of existing Haskell functions. For example, sort is an existing Haskell function which can be imported as shown below.

Prelude>  import Data.List
Prelude>

Prelude>  :type sort
sort :: Ord a => [a] -> [a]
Prelude>

Prelude>  sort [1,7,2,9,5,4,3]
[1,2,3,4,5,7,9]
it :: (Ord a, Num a) => [a]
Prelude>

Your own sort might look something like the following:

module Mysort where

mysort              :: Ord a => [a] -> [a]
mysort          [] = []
mysort (x : xs) = mysort smaller ++ [x] ++ mysort larger
    where
        smaller = [a | a <- xs, a <= x ]
        larger   = [b | b <- xs, b > x ]

"Copy Paste" the above module into you text editor and "Save As" Mysort.hs to your working directory.

Load Mysort.hs into GHCi.

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

Call the mysort function in GHCi.

Prelude> mysort [1,7,2,9,5,4,3]
[1,2,3,4,5,7,9]
it :: (Ord a, Num a) => [a]
Prelude>

Playing and comparing your functions to the existing Haskell functions makes learning more fun. 

-- foldr.

Prelude>  :type foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
Prelude>  

-- Using foldr for sum.

Prelude>  let sum xs = foldr (+) 0 xs
sum :: Num b => [b] -> b
Prelude>

Prelude>  :type sum
sum :: Num b => [b] -> b
Prelude>

Prelude>  sum [1,2]
3
it :: Num b => b
Prelude>  

Prelude>  let mysum = foldr (+) 0
mysum :: Num b => [b] -> b
Prelude>

Prelude>  :type mysum
mysum :: Num b => [b] -> b
Prelude>

Prelude>  mysum [1,2]
3
it :: Num b => b
Prelude> 

-- Using foldr for product.

Prelude>  let product xs = foldr (*) 1 xs
product :: Num b => [b] -> b
Prelude>

Prelude>  :type product
product :: Num b => [b] -> b
Prelude>

Prelude>  product [1,2]
2
it :: Num b => b
Prelude>  

Prelude>  let myproduct = foldr (*) 1
myproduct :: Num b => [b] -> b
Prelude>

Prelude>  :type myproduct
myproduct :: Num b => [b] -> b
Prelude>

Prelude>  myproduct [1,2]
2
it :: Num b => b
Prelude> 

-- Using foldr for concat.

Prelude>  let concat xs = foldr (++) [] xs
concat :: [[a]] -> [a]
Prelude>

Prelude>  :type concat
concat :: [[a]] -> [a]
Prelude>

Prelude>  concat [['a','b'],['c','d']]
"abcd"
it :: [Char]
Prelude>  

Prelude>  let myconcat = foldr (++) []
myconcat :: [[a]] -> [a]
Prelude>

Prelude>  :type myconcat
myconcat :: [[a]] -> [a]
Prelude>

Prelude>  myconcat [['a','b'],['c','d']]
"abcd"
it :: [Char]
Prelude>  

J.  MAKE UP YOUR OWN FUNCTIONS

Prelude>  let search xs y = [i | (i,x) <- zip [0..] xs, x==y]
search :: (Num t, Eq a, Enum t) => [a] -> a -> [t]
Prelude>

Prelude>  :type search
search :: (Enum t, Eq a, Num t) => [a] -> a -> [t]
Prelude>

Prelude>  search "bookshop" 'o'
[1,2,6]
it :: (Num t, Enum t) => [t]
Prelude>  

Module:

module MyRepeat where

myRepeat :: a -> [a]
myRepeat x = x:myRepeat x  -- This is an infinite loop.

GHCi:

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

Prelude>  take 10 $ myRepeat 5  -- Used take function to prevent infinite loop.
[5,5,5,5,5,5,5,5,5,5]
Prelude>

The take function has two arguments. The first argument is the number of elements that will be taken; i.e., 10. The second argument is the list of elements the selection will be made from, in the above case an infinite list of 5s.

The function signature for take is:

take :: Int -> [a] -> [a]

Using the $ is the same as typing:

take 10 (myRepeat 5)

K.  SEE HOW MANY DIFFERENT WAYS YOU CAN WRITE THE SAME FUNCTION

-- The function f yields a sum of positive squares.

module F where

f      :: (Num a, Ord a) => [a] -> a
f xs = sum [ x*x | x <- xs, x > 0 ]

"Copy Paste" the module F shown above to your text editor and "Save As" F.hs in your working directory.

Load the module F into GHCi as shown below:

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

Call the function f as follows:

Prelude>  f [1,2,3]  -- sum == 1 + 4 + 9 == 14
14
it :: (Ord a, Num a) => a
Prelude>

Alternatively you can use let as shown below:

Prelude>  let f xs = sum [ x*x | x <- xs, x >0 ]
f :: (Ord b, Num b) => [b] -> b
Prelude>

Prelude>  :type f
f :: (Ord b, Num b) => [b] -> b
Prelude>
Prelude>  f [1,2,3]
14
it :: (Ord b, Num b) => b
Prelude>  

-- The function f1 yields a sum of positive squares.

module F1 where

f1      :: (Num b, Ord b) => [b] -> b
f1 xs = foldr add 0 (map square (filter positive xs))
   where
   add x y     = x + y
   square x   = x * x
   positive x = x > 0

"Copy Paste" the module F1 shown above to your text editor and "Save As" F1.hs in your working directory.

Load the module F1 into GHCi as shown below:

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

Call function f1 as shown below:

Prelude>  :type f1
f1 :: (Num b, Ord b) => [b] -> b
Prelude>

Prelude>  f1 [1,2,3]     -- (1*1) + (2*2) + (3*3) = 14
14
it :: (Ord b, Num b) => b
Prelude>  

II.  CONCLUSION

In this tutorial, you have received an introduction to Haskell user defined functions.

III.  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.