UNIT 5
UNIT 5
UNIT 5
A mathematical function is a mapping of members of one set, called the domain set, to another
set, called the range set. A function definition specifies the domain and range sets, either
explicitly or implicitly, along with the mapping. The mapping is described by an expression or,
in some cases, by a table.
One of the fundamental characteristics of mathematical functions is that the evaluation order of
their mapping expressions is controlled by recursion and conditional expressions, rather than by
the sequencing and iterative repetition that are common to programs written in the imperative
programming languages.
Another important characteristic of mathematical functions is that because they have no side
effects and cannot depend on any external values, they always map a particular element of the
domain to the same element of the range.
Function definitions are often written as a function name, followed by a list of parameters in
parentheses, followed by the mapping expression. For example,
cube(x) ≡ x * x * x,
where x is a real number. In this definition, the domain and range sets are the real numbers. The
symbol ≡ is used to mean “is defined as.” The parameter x can represent any member of the
domain set, but it is fixed to represent one specific element during evaluation of the function
expression.
The parameter x is bound to 2.0 during the evaluation and there are no unbound parameters.
Furthermore, x is a constant (its value cannot be changed) during the evaluation.
A lambda expression specifies the parameters and the mapping of a function. The lambda
expression is the function itself, which is nameless. For example, consider the following lambda
expression:
λ(x)x * x * x
Church defined a formal computation model (a formal system for function definition, function
application, and recursion) using lambda expressions. This is called lambda calculus. Lambda
calculus can be either typed or untyped. Untyped lambda calculus serves as the inspiration for
the functional programming language..
Functional form
A higher- order function, or functional form, is one that either takes one or more functions as
parameters or yields a function as its result, or both. One common kind of functional form is
function composition, which has two functional parameters and yields a function whose value is
the first actual parameter function applied to the result of the second. Function composition is
written as an expression, using ° as an operator, as in
h≡f°g
For example, if
f(x) ≡ x + 2
g(x) ≡ 3*x
then h is defined as
h(x) ≡ f ( g(x)), or h(x) ≡ (3*x) x+ 2
Apply-to-all is a functional form that takes a single function as a parameter. If applied to a list of
parameters, apply-to-all applies its functional parameter to each of the values in the list
parameter and collects the results in a list or sequence. Apply-to-all is denoted by a. Consider the
following example:
Let
h(x) ≡ x*x
then
a(h,(2,3,4)) yields (4, 9, 16)
languages. The storage of intermediate results is still required, but the details are hidden from the
programmer.
A purely functional programming language does not use variables or assignment
statements, thus freeing the programmer from concerns related to the memory cells, or state, of
the program. Without variables, iterative constructs are not possible, for they are controlled by
variables. Repetition must be specified with recursion rather than with iteration. Programs are
function definitions and function application specifications, and executions consist of evaluating
function applications. Without variables, the execution of a purely functional program has no
state in the sense of operational and denotational semantics. The execution of a function always
produces the same result when given the same parameters. This feature is called referential
transparency. It makes the semantics of purely functional languages far simpler than the
semantics of the imperative languages.
A functional language provides a set of primitive functions, a set of functional forms to
construct complex functions from those primitive functions, a function application operation, and
some structure or structures for representing data. These structures are used to represent the
parameters and values computed by functions. If a functional language is well designed, it
requires only a relatively small number of primitive functions.
INTRODUCTION TO SCHEME:
The Scheme language is a language with simple syntax and semantics, Scheme is well
suited to educational applications, such as courses in functional programming, and also to
general introductions to programming.
The Scheme Interpreter:
A Scheme interpreter repeatedly reads an expression typed by the user (in the form of a list),
interprets the expression, and displays the resulting value. This form of interpreter is also used by
Ruby and Python. Expressions are interpreted by the function EVAL. Expressions that are calls
to primitive functions are evaluated in the following way: First, each of the parameter
expressions is evaluated, in no particular order. Then, the primitive function is applied to the
parameter values, and the resulting value is displayed.
Comments in Scheme are any text following a semicolon on any line.
Primitive Numeric Functions:
Scheme includes primitive functions for the basic arithmetic operations. These are +, −, *, and /,
for add, subtract, multiply, and divide. * and + can have zero or more parameters. If * is given no
parameters, it returns 1; if + is given no parameters, it returns 0. + adds all of its parameters
together. * multiplies all its parameters together. / and − can have two or more parameters.
Expression Value
42 42
(* 3 7) 21
(+ 5 7 8) 20
(− 5 6) −1
(− 15 7 2) 6
(− 24 (* 4 3)) 12
There are a large number of other numeric functions in Scheme, among them MODULO,
ROUND, MAX, MIN, LOG, SIN, and SQRT. SQRT returns the square root of its numeric
parameter, if the parameter’s value is not negative. If the parameter is negative, SQRT yields a
complex number.
Defining Functions:
In Scheme, a nameless function actually includes the word LAMBDA, and
is called a lambda expression. For example,
(LAMBDA (x) (* x x))
is a nameless function that returns the square of its given numeric parameter. This function can
be applied in the same way that named functions are: by placing it in the beginning of a list that
contains the actual parameters. For example, the following expression yields 49:
((LAMBDA (x) (* x x)) 7)
In this expression, x is called a bound variable within the lambda expression. During the
evaluation of this expression, x is bound to 7. A bound variable never changes in the expression
after being bound to an actual parameter value at the time evaluation of the lambda expression
begins.
The Scheme special form function DEFINE serves two fundamental needs of Scheme
programming: to bind a name to a value and to bind a name to a lambda expression.
The simplest form of DEFINE is one used to bind a name to the value of an expression. This
form is
(DEFINE symbol expression)
For example,
(DEFINE pi 3.14159)
(DEFINE two_pi (* 2 pi))
If these two expressions have been typed to the Scheme interpreter and then pi is typed, the
number 3.14159 will be displayed; when two_pi is typed, 6.28318 will be displayed.
Names in Scheme can consist of letters, digits, and special characters except parentheses; they
are case insensitive and must not begin with a digit.
The second use of the DEFINE function is to bind a lambda expression to a name. In this case,
the lambda expression is abbreviated by removing the word LAMBDA. To bind a name to a
lambda expression, DEFINE takes two lists as parameters. The first parameter is the prototype of
a function call, with the function name followed by the formal parameters, together in a list. The
second list contains an expression to which the name is to be bound. The general form of such a
DEFINE is
(DEFINE (function_name parameters)
(expression)
)
The following example call to DEFINE binds the name square to a functional expression that
takes one parameter:
(DEFINE (square number) (* number number))
After the interpreter evaluates this function, it can be used, as in
(square 5)
which displays 25.
Output Functions:
Scheme includes a few simple output functions, but when used with the interactive
interpreter, most output from Scheme programs is the normal output from the interpreter,
displaying the results of applying EVAL to top-level functions.
Numeric Predicate Function:
A predicate function is one that returns a Boolean value.
Function Meaning
= Equal
<> Not equal
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
EVEN? Is it an even number?
ODD? Is it an odd number?
ZERO? Is it zero?
Notice that the names for all predefined predicate functions that have words for names end with
question marks. In Scheme, the two Boolean values are #T and #F (or #t and #f), although the
Scheme predefined predicate functions return the empty list ,(),for false.
Control Flow:
Scheme uses three different constructs for control flow: one similar to the selection construct of
the imperative languages and two based on the evaluation control used in mathematical
functions. The Scheme two-way selector function, named IF, has three parameters: a predicate
expression, a then expression, and an else expression. A call to IF has the form
(IF predicate then_expression else_expression)
(DEFINE (factorial n)
(IF (<= n 1)
1
(* n (factorial (− n 1)))
))
List Functions:
One of the more common uses of the Lisp-based programming languages is list
processing. This subsection introduces the Scheme functions for dealing with lists.
Suppose we have a function that has two parameters, an atom and a list, and the purpose of the
function is to determine whether the given atom is in the given list. Neither the atom nor the list
should be evaluated; they are literal data to be processed. To avoid evaluating a parameter, it is
first given as a parameter to the primitive function QUOTE, which simply returns it without
change. The following examples illustrate QUOTE:
(QUOTE A) returns A
(QUOTE (A B C)) returns (A B C)
Calls to QUOTE are usually abbreviated by preceding the expression to be quoted with an
apostrophe (') and leaving out the parentheses around the expression. Thus, instead of (QUOTE
(A B)), '(A B) is used.
The necessity of QUOTE arises because of the fundamental nature of Scheme data and code
have the same
Following are additional examples of the operations of CAR and CDR:
(CAR '(A B C)) returns A
(CAR '((A B) C D)) returns (A B)
(CAR 'A) is an error because A is not a list
(CAR '(A)) returns A
(CAR '()) is an error
(CDR '(A B C)) returns ( C)
(CDR '((A B) C D)) returns (C D)
(CDR 'A) is an error
(CDR '(A)) returns ()
(CDR '()) is an error.
Two machine instructions, also named CAR (contents of the address part of a register) and CDR
(contents of the decrement part of a register), that extracted the associated fields.
list as the actual parameter. That event must force the function to return #F. Either the list is
mpty on some call, in which case #F is returned, or a match is found and #T is returned.
Altogether, there are three cases that must be handled in the function: an empty input list, a
match between the atom and the CAR of the list, or a mismatch between the atom and the CAR
of the list, which causes the recursive call.
These three are the three parameters to COND, with the last being the default
case that is triggered by an ELSE predicate. The complete function follows:
(DEFINE (member atm a_list)
(COND
((NULL? a_list) #F)
((EQ? atm (CAR a_list)) #T)
(ELSE (member atm (CDR a_list)))
))
This form is typical of simple Scheme list-processing functions. In such functions, the data in
lists are processed one element at a time. The individual elements are specified with CAR, and
the process is continued using recursion on the CDR of the list.
LET
LET is a function that creates a local scope in which names are temporarily bound to the values
of expressions. It is often used to factor out the common subexpressions from more complicated
expressions.
The following example illustrates the use of LET. It computes the roots of a given quadratic
equation, assuming the roots are real. The mathematical definitions of the real (as opposed to
complex) roots of the quadratic equation ax2 + bx + c are as follows:
root1 = (-b + sqrt(b2 - 4 ac))/2a and
root 2 = (-b - sqrt(b2 - 4 ac))/2 a
(DEFINE (quadratic_roots a b c)
(LET (
(root_part_over_2a
(/ (SQRT (− (* b b) (* 4 a c))) (* 2 a)))
(minus_b_over_2a (/ (− 0 b) (* 2 a)))
)
(LIST (+ minus_b_over_2a root_part_over_2a)
(− minus_b_over_2a root_part_over_2a))
))
This example uses LIST to create the list of the two values that make up the result.
ML does not allow overloaded functions. Each of the following definitions is also legal:
fun square(x : real) = x * x;
fun square(x) = (x : real) * x;
fun square(x) = x * (x : real);
The ML selection control flow construct is similar to that of the imperative languages. It has the
following general form:
if expression then then_expression else else_expression
The first expression must evaluate to a Boolean value.
For example,
without using this pattern matching, a function to compute factorial could be written as follows:
fun fact(n : int): int = if n <= 1 then 1
else n * fact(n − 1);
Multiple definitions of a function can be written using parameter pattern matching. The different
function definitions that depend on the form of the parameter are separated by an OR symbol (|).
For example, using pattern matching, the factorial function could be written as follows:
fun fact(0) = 1
| fact(1) = 1
| fact(n : int): int = n * fact(n − 1);
If fact is called with the actual parameter 0, the first definition is used; if the actual parameter is
1, the second definition is used; if an int value that is neither 0 nor 1 is sent, the third definition is
used.
In ML, names are bound to values with value declaration statements of the form
val new_name = expression;
For example,
val distance = time * speed;
The val statement binds a name to a value, but the name cannot be later rebound to a new value.
Actually, if you do rebind a name with a second val statement, it causes a new entry in the
evaluation environment that is not related to the previous version of the name. In fact, after the
new binding, the old evaluation environment entry (for the previous binding) is no longer visible.
Also, the type of the new binding need not be the same as that of the previous binding. val
statements do not have side effects. They simply add a name to the current evaluation
environment and bind it to a value.
The normal use of val is in a let expression.
Consider the following
example:
let val radius = 2.7
val pi = 3.14159
Each element on which the predicate returns true is added to a new list, which is the return value
of the function. Consider the following use of filter:
filter(fn(x) => x < 100, [25, 1, 50, 711, 100, 150, 27, 161, 3]);
ML has a binary operator for composing two functions, o (a lowercase “oh”). For example, to
build a function h that first applies function f and then applies function g to the returned value
from f, we could use the following:
val h = g o f;
The process of currying replaces a function with more than one parameter with a function with
one parameter that returns a function that takes the other parameters of the initial function.ML
functions that take more than one parameter can be defined in curried form by leaving out the
commas between the parameters (and the delimiting parentheses). For example, we could have
the following:
fun add a b = a + b;
Although this appears to define a function with two parameters, it actually defines one with just
one parameter. The add function takes an integer parameter (a) and returns a function that also
takes an integer parameter (b). A call to this function also excludes the commas between the
parameters, as in the following:
add 3 5;
This call to add returns 8, as expected.
Curried functions are interesting and useful because new functions can be constructed from them
by partial evaluation. Partial evaluation means that the function is evaluated with actual
parameters for one or more of the leftmost formal parameters. For example, we could define a
new function as follows:
fun add5 x = add 5 x;
The add5 function takes the actual parameter 5 and evaluates the add function with 5 as the value
of its first formal parameter. It returns a function that adds 5 to its single parameter, as in the
following:
val num = add5 10;
The value of num is now 15.
We could create any number of new functions from the curried function add to add any specific
number to a given parameter.
state this formally, suppose the list to be sorted is in an array named list that has a subscript range
1 . . . n. The concept of sorting the elements of the given list, named old_list, and placing them in
a separate array, named new_list, can then be expressed as follows:
sort(old_list, new_list) ⊂ permute(old_list, new_list) ∩ sorted(new_list)
sorted(list) ⊂ ∀j such that 11 ≤j < n, list(j)≤ list(j + 1)
where permute is a predicate that returns true if its second parameter array is a permutation of its
first parameter array.
and the left side is the consequent, or then part. If the antecedent of a Prolog statement is true,
then the consequent of the statement must also be true.
Conjunctions contain multiple terms that are separated by logical AND operations. In
Prolog, the AND operation is implied.
The general form of the Prolog headed Horn clause statement is
consequence :- antecedent_expression.
It is read as follows: “consequence can be concluded if the antecedent expression is true
or can be made to be true by some instantiation of its variables.”
For example,
ancestor(mary, shelley) :- mother(mary, shelley).
states that if mary is the mother of shelley, then mary is an ancestor of shelley.
Goal Statements:
These statements are the basis for the theorem-proving model. The theorem is in the form
of a proposition that we want the system to either prove or disprove. In Prolog, these
propositions are called goals, or queries. For example, we could have
man(fred).
to which the system will respond either yes or no. The answer yes means that the system has
proved the goal was true. The answer no means that either the goal was determined to be false or
the system was simply unable to prove it.
matches that lead to the goal. This approach is called bottom-up resolution, or forward chaining.
The alternative is to begin with the goal and attempt to find a sequence of matching propositions
that lead to some set of original facts in the database. This approach is called top-down
resolution, or backward chaining.
The following example illustrates the difference between forward and backward chaining.
Consider the query:
man(bob).
Assume the database contains
father(bob).
man(X) :- father(X).
Forward chaining would search for and find the first proposition. The goal is then inferred by
matching the first proposition with the right side of the second rule (father(X)) through
instantiation of X to bob and then matching the left side of the second proposition to the goal.
Backward chaining would first match the goal with the left side of the second proposition
(man(X)) through the instantiation of X to bob. As its last step, it would match the right side of
the second proposition (now father(bob)) with the first proposition.
The next design question arises whenever the goal has more than one structure, as in our
example. The question then is whether the solution search is done depth first or breadth first. A
depth-first search finds a complete sequence of propositions—a proof—for the first subgoal
before working on the others. A breadth-first search works on all subgoals of a given goal in
parallel.
When a goal with multiple subgoals is being processed and the system fails to show the truth of
one of the subgoals, the system abandons the subgoal it cannot prove. It then reconsiders the
previous subgoal, if there is one, and attempts to find an alternative solution to it. This backing
up in the goal to the reconsideration of a previously proven subgoal is called backtracking. A
new solution is found by beginning the search where the previous search for that subgoal
stopped.
Simple Arithmetic:
Prolog supports integer variables and integer arithmetic. Originally, the arithmetic operators
were functors, so that the sum of 7 and the variable X was formed with +(7, X) Prolog now
allows a more abbreviated syntax for arithmetic with the is operator. This operator takes an
arithmetic expression as its right operand and a variable as its left operand. All variables in the
expression must already be instantiated, but the left-side variable cannot be previously
instantiated. For example, in A is B / 17 + C. if B and C are instantiated but A is not, then this
clause will cause A to be instantiated with the value of the expression.
This basic information can be coded as facts, and the relationship
between speed, time, and distance can be written as a rule, as in the following:
speed(ford, 100).
speed(chevy, 105).
speed(dodge, 95).
speed(volvo, 80).
time(ford, 20).
time(chevy, 21).
time(dodge, 24).
time(volvo, 24).
distance(X, Y) :- speed(X, Speed),time(X, Time),
Y is Speed * Time.
Now, queries can request the distance traveled by a particular car. For example, the query
distance(chevy, Chevy_Distance).
instantiates Chevy_Distance with the value 2205.
MULTI-PARADIGM LANGUAGES:
A programming language that supports both procedural and object-oriented programming
concepts. C++ is one of the multi paradigm language because it has the concepts of
programming language but added the object oriented concepts also.