Sunday, January 27, 2013

HASKELL FUNCTIONS INTRODUCTION

HASKELL FUNCTIONS INTRODUCTION

REVISED: Friday, February 9, 2024




In this tutorial, you will receive an introduction to Haskell functions.

I.  FUNCTIONS

A function is a rule which assigns to each number in its domain another number. The domain consists of all numbers for which the rule makes sense. A function takes one input value and produces one output value. A function maps a domain to a range. Examples comparing math functions to their Haskell functions are as follows:

MATH             HASKELL
f ( x )                f x
f ( x, y )            f x y
f ( g ( x ) )        f (g x)
f ( x, g ( y ) )    f x (g y)
f ( x )  g ( y )    f x * g y

II.  HASKELL FUNCTIONS

Functions are used to model and solve problems. Functions should comply with the "Abstraction Principle" which says nothing should be duplicated or in other words do not repeat yourself. We want to abstract out the commonality in our programs so we do not repeat our code. This pattern of spotting a repeated idiom, then abstracting it so we can reuse and write less code, is a common aspect of Haskell programming.

The syntax for a Haskell function definition is function name, argument names, an equal sign, then the body of the function. The body of the function can also be thought of as an expression. Function arguments are values passed from the left side of the equal sign to parameters used within the body of the function on the right side of the equal sign.

funcName arg1 arg2 = param1 param2   -- Parameter operations compute to a returned value.

Prelude>  let times x y = x * y
times :: Num a => a -> a -> a
Prelude>

Prelude>  times 2 3
6
it :: Num a => a
Prelude>

The = symbol in Haskell can be read, "The function name on the left is defined to be the expression on the right." The expression on the right explains what the function does. In the example above the function named times is defined to be the parameter expression x * y.  What does the function times do? times multiplies x by y. 

In Haskell you never assign a variable, instead you bind a name to a value.    Local variables may be bound using let, which declares a new binding for a variable with local scope.

For example, when we apply the argument values 2 and 3 to the function times, the argument variables x and y are given or "bound to" the argument values 2 and 3. When x is bound to 2 and y is bound to 3; the result is the parameter expression 2 * 3.

Regular variables, such as arguments and parameters, always begin with a lowercase letter. A Haskell parameter variable provides a way to give a name to an expression. After a parameter variable is bound to; i.e., associated with a particular expression, its value does not change.

A function is a single expression, not a sequence of statements. The value of the expression is the result of the function. A function has input and output. A function describes how to produce the output from its input. Each Haskell function has a single result. The right hand side is equal to the left hand side in the sense that it might be substituted for it anywhere in the program.

In Haskell, a predicate is a function that returns a Boolean value. It is used to test whether a value satisfies a certain condition. For example, the following predicate checks if a number is even:

even :: Int -> Bool
even n = n `mod` 2 == 0

A. FUNCTION SIGNATURE

Haskell does not require function signatures; however, when you become an experienced Haskell programmer your first step, when you write a Haskell function, will be to write the function's signature. The signature declares the types of the arguments and the type of the result. Signatures are self documenting. 

times :: Num a => a -> a -> a

The operator :: is pronounced ''has type", and it separates the name of the function from the type definitions. The left side of the :: operator is in the "value namespace," and the right side of the :: operator is in the "type namespace." If there are more than one function input parameter, each parameter is separated from the next parameter by a -> kleisli arrow representing a morphism. The type of the last input parameter in the list, is the type of the output of the function. And, a morphism is just a systematic way of turning solutions of one system into solutions of another.

It is common Haskell practice to keep the names of type variables very short. One letter is common. Long names are rarely used. Type signatures are usually brief. We gain more in readability by keeping names short, than we would by making them descriptive.

Prelude> z (x,y) = (y,x)  -- Notice the position reversal of the arguments and parameters.

Prelude> z (12,21)

(21,12)

Prelude> :t z               
-- :t asks ghci for the type of z
z :: (b, a) -> (a, b)         -- Notice the position reversal in the type  signature.
Prelude>

B. FUNCTION BODY

The second step in writing a Haskell function is writing the body of the function, an expression, which is the function definition. In the example below the body of the function is everything to the right of the equal sign; i.e., x * y. The body of a function evaluates to a value which is returned by the function. 

times x y = x * y

Functions are defined and called in a similar manner. When a function is "defined", the function name is followed by arguments separated by spaces, an = sign read as "is", and a definition of what the function does. When a function is "called", the function name is followed by arguments separated by spaces.

In GHCI we can use the let keyword to define a function. Coding let times x y = x * y inside GHCI is the equivalent of writing times x y = x * y in a script and then loading it.

For example:

C. SCRIPT

First, "Copy Paste" the following Haskell script into your text editor; and "File, Save As" times.hs to your working directory.

-- Example of a Script
times :: Num a => a -> a -> a
times x y = x * y

Second, change to your working directory in GHCi. I used the following commands:


Prelude> :cd C:\Users\Tinnel\Haskell2024\newProjects\
Prelude>


Third, until you are comfortable with the GHCi interpreter, use the following to check to make sure the path was changed correctly: 

Prelude> :show paths
current working directory:
     C:\Users\Tinnel\Haskell2024\newProjects\
module import search paths:
     .
Prelude>

Fourth, load the script into the Haskell GHCi "interactive mode" as follows:

Prelude>  :load times
[1 of 1] Compiling Main             ( times.hs, interpreted )
Ok, 1 module loaded.
*Main>  

Fifth, call the function times and have it process the numbers 2 3 as follows:

*Main>  times 2 3
6
it :: Num a => a
*Main>  

D. INTERPRETER

Prelude> let times x y = x * y
times :: Num a => a -> a
Prelude>

Prelude> times 2 3
6
it :: Num a => a
Prelude>

Prelude> :type times
times :: Num a => a -> a -> a
Prelude>

Haskell does not require that a value or function be declared or defined in a source file script before it is used. It is perfectly normal for a definition to come after the first place it is used.

III.  HASKELL PURE FUNCTIONS

Pure code is code that does no input or output (IO) it is code outside of the IO monad.

Safe Haskell pure functions are a subset of modern Haskell that provide type safety, referential transparency, strict module encapsulation, modular reasoning and semantic consistency.

A function is pure when it has no side effects. Having no side effects means a function does not alter a state or mutate any data.

Variables are used in a computer program to store data. Each variable has its own computer memory storage location. The program's state, at any given point in the program's execution, is the contents of these memory locations.

Mutate is the act or process of being altered or changed.

Haskell functions have values that are manipulated; however, the values cannot be changed in-place. Instead of mutating the state of objects, Haskell functions return a new object with the changes we want. All Haskell functions simply receive values and produce new values. A function called twice with the same arguments will always produce the same result. This is called referential transparency.

IV. HASKELL IMPURE FUNCTIONS

Impure code is code that does input or output (IO), it is code inside the IO monad. For example, the IO can be different each time evaluated; however, the IO instructions evaluated are always the same.

V.  HASKELL INFIX FUNCTION VERSUS PREFIX FUNCTION

In infix form, an operator appears between its operands. In prefix form the operator is enclosed in parentheses and precedes its arguments.

The - operator is Haskell's only unary operator, and we cannot mix it with infix operators. If we want to use the unary minus near an infix operator, we must wrap the expression it applies to in parentheses.

Infix functions are ones that are composed of symbols, rather than letters. For instance; +, *, and ++ are all infix functions. You can use them in non-infix mode by enclosing them in parentheses. Enclosing them in parentheses converts them into curried functions. 

The infix function * is a function that takes two numbers and multiplies them. We call * by placing it between numbers.

Prelude> :type (*)
(*) :: Num a => a -> a -> a
Prelude>

The function signature shown above states * is a function which, for some type a which is an instance of Num, takes a value of type a and produces another function which takes a value of type a and produces a value of type a.

In the (*) function signature Num a is a class constraint; Num is the name of the class and a is a type name. The function signature can be read for any type a that is an instance of the class Num of numeric types, function (*) has type a -> a -> a.  A type that has one or more class constraints is referred to as being overloaded.  The => operator indicates a class constraint. Most of the numeric functions in Prelude are overloaded.

`Backticks` make a function name into an infix operator.

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

The type of ++ means that for a given type a, ++ takes two lists of a's and produces a new list of a's.

Most functions that are not used with numbers are prefix functionsPrefix functions; for example, map, can be made infix by enclosing them in backquotes (the ticks on the tilde key on American keyboards).

VI. FUNCTION APPLICATION

Function application is "calling" a function. We call a function by typing the function name followed by a space and then the function's arguments each separated by a space followed by Enter. Function application has the strongest binding power or highest precedence, higher than all other operators.

VII. FUNCTION COMPOSITION

Function composition is the act of pipelining the result of one function, to the input of another, creating an entirely new function. This can be done with any two functions, where the argument type of the first is the return type of the second.

The dot . is the function composition operator, dot is read as "after". The dot . operator has very high precedence, surpassed only by function application.

A. PIPELINING FUNCTIONS

Prelude>  :type (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude>

Prelude>  :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in `GHC.Base'
infixr 9 .          -- infix operator, right associative, precedence 9.
Prelude>  

As shown below we can pipeline the results of function x into function y and create a new function z by using the dot function composition operator.

Prelude> x = negate
Prelude> x 4.0
-4.0


Prelude> y = sqrt
Prelude> y 16
4.0

Prelude> z = x.y  -- negate.sqrt  

Prelude> z 16      -- First sqrt of 16, then negate 16.    
-4.0
Prelude>


B. NESTING FUNCTIONS

Function composition can also be described as the nesting of two or more functions to form a single new function.

Prelude> [ x | x <- [1..10] ]
[1,2,3,4,5,6,7,8,9,10]
it :: (Num t, Enum t) => [t]
Prelude>

The symbols | and <- are read as “such that” and “is drawn from” respectively, and the expression x <- [1. .10] is called a generator.

Functions can be passed to other functions. Function composition is clear and concise and often used to make functions to pass to other functions.

As shown below the same problem can be solved using Haskell script source code.

module WIP where
wip = print [x | x <- [1..10]]

Prelude>  :load WIP
[1 of 1] Compiling WIP              ( WIP.hs, interpreted )
Ok, 1 module loaded.
*WIP>

*WIP>  wip
[1,2,3,4,5,6,7,8,9,10]
*WIP>  

VIII. CURRYING

Prelude> :type curry
curry :: ((a, b) -> c) -> a -> b -> c
Prelude> 

Prelude> :type uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
Prelude> 

Currying changes a function that takes more than one argument into a function that takes one argument. If another argument is needed, currying can be used to return another function.

All functions in Haskell take a single argument. Therefore, all functions in Haskell are considered curried; one argument in, one result out.

Prelude> (2^) 5  -- Notice there is no space between 2 and ^.  2*2*2*2*2 = 32.
32
it :: Num a => a
Prelude>

Prelude> 2^ 5
32
it :: Num a => a


Prelude>
Prelude> (^2) 5  -- Notice there is no space between ^ and 2.  5*5 = 25.
25
it :: Num a => a
Prelude>

Prelude> 5 ^2
25
it :: Num a => a
Prelude>

Note the difference between (2^), which is a function that takes a number and returns 2 to that power, and (^2), which is a function that takes a number and squares it. Currying is the process of applying a multi-argument function to one argument and producing a new function that uses the remaining arguments.

An uncurried function is a function which cannot be applied to the arguments one at a time.

IX. EXPONENT

f(x,y) = x y

f(x,y) = x -y for negative exponents calculate the positive exponent y then take the reciprocal 1/(y).

f x y = x ^ y

Haskell has three exponentiation operators: (^), (^^) and (**).

(^) is for non-negative integral exponentiation.

(^^) is for either positive or negative integer exponentiation.

(**) is for either positive or negative floating point exponentiation.

Prelude>  :type (^)
(^) :: (Num a, Integral b) => a -> b -> a
Prelude>

Prelude>  let f x = 2^ x
Prelude>

Prelude>  f 5     -- 2*2*2*2*2 = 32.
32
Prelude>

Prelude>  :type (^^)
(^^) :: (Integral b, Fractional a) => a -> b -> a
Prelude>

Prelude>  let f x = 2^^ x
Prelude>

Prelude>  f 5
32.0
Prelude>

Prelude>  f (-5)  -- The reciprocal of 32 is 1/32 = 3.125e-2 = .03125
3.125e-2
Prelude>

Prelude>  :type (**)
(**) :: Floating a => a -> a -> a
Prelude>  

Prelude>  let f x = 2** x
Prelude>

Prelude>  f 5
32.0
Prelude>

Prelude>  f (-5)
3.125e-2
Prelude>

X. SQUARE ROOT

f(x,y) = x 1/y = y√x1 = y√x

As shown above, in mathematics the radical sign symbol √ is placed before a number to indicate the extraction of a root, especially a square root. 

For square root let y equal 2 then f(x,2) = x 1/2 = 2√x1 = 2√x = √x

Prelude>  let f x = x** 0.5
Prelude>

Prelude>  f 25
5.0
Prelude>

Or, since sqrt is part of Prelude you do not have to import anything, you can just use the Haskell sqrt keyword to get the square root.

Prelude>  sqrt 25
5.0
it :: Floating a => a
Prelude>

Prelude>  :type sqrt
sqrt :: Floating a => a -> a
Prelude>  

XI. MAIN

GHC looks for a module named Main to find the main action. Therefore, each Haskell program needs to have one function named main.  The function main is the starting and ending point for every Haskell program. If you omit a module header on a Haskell file, the module name defaults to Main.

The execution of a Haskell program is an evaluation of the symbol main to which we have bound an IO action. main must have type IO a where a may be any return value. If something has type IO a, then it is executable. If something does not have type IO a, then it is not executable.

XII. CONCLUSION

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

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