0% found this document useful (0 votes)
131 views49 pages

PPL Unit 5

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
131 views49 pages

PPL Unit 5

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

UNIT 5

Functional Programming Languages: An


Overview
Functional programming languages are based
on mathematical functions, aiming for a solid
theoretical foundation and a design that is
closer to the user's perspective. Unlike
imperative languages, which are directly tied to
the von Neumann architecture, functional
languages prioritize suitability for software
development over raw efficiency.

Key Concepts

* Mathematical Function:
* A mathematical function is a mapping
between elements of two sets: the domain and
the range.
* In functional programming, functions are
treated as first-class citizens, meaning they can
be assigned to variables, passed as arguments,
and returned as results.

* Lambda Expression:
* Lambda expressions define anonymous
functions, meaning they don't have a specific
name.
* They consist of a parameter list and an
expression that operates on those parameters.
* Example: (x) => x * x defines a function
that squares its input.
* Functional Form:Also known as higher-
order functions, functional forms either take
functions as input or return functions as output.
* Function Composition:
Functional composition is a powerful technique
in functional programming that allows you to
combine multiple functions into a single, more
complex function. It's like chaining functions
together, where the output of one function
becomes the input of the next.

How it Works:
An example illustrate this concept
h=f°g

This means that the function h is created by


composing functions f and g. The result of
applying h to an input x is the same as applying
g to x first, and then applying f to the result of
g(x).
Example
Let's consider the following functions:
f(x) = x + 2
g(x) = 3 * x

Composing these functions would give us:


h(x) = f(g(x)) = f(3 * x) = (3 * x) + 2

So, if we apply h to the value x = 3, we get:


h(3) = (3 * 3) + 2 = 11
Example in Python
def add(x, y):

return x + y

def square(x):
return x * x

# Function composition
result = add(square(2), square(3))
print(result) # Output: 13

Advantages of Functional Programming


* Readability: Functional code often tends to be
more concise and easier to understand due to
the emphasis on pure functions and declarative
style.
* Modularity: Functional programs are often
composed of smaller, independent functions,
promoting code reuse and maintainability.

* Parallelism: Functional programming can be


well-suited for parallel and

concurrent programming as functions are


typically side-effect-free, making them easier to
parallelize.
* Testing: Pure functions are easier to test as
their output depends solely on their input,
without external side effects.

fundamentals of functional programming


languages :
Mathematical Functions:
* Functional programming aims to mimic
mathematical functions to the greatest extent
possible.
* This means that functions should be pure,
meaning they should always produce the same
output for a given input, without any side
effects.
* Declarative Style:
* Functional programming emphasizes a
declarative style, where you specify what you
want to compute, rather than how to compute it.
* This contrasts with imperative
programming, which focuses on step-by-step
instructions.

Immutability:
* Functional programs often use immutable
data structures, where values cannot be changed
once they are created.
* This simplifies reasoning about program
behavior and reduces the likelihood of errors.
* Higher-Order Functions:
* Higher-order functions are functions that can
take other functions as arguments or return
functions as results.
* This allows for powerful abstractions and
the creation of flexible and reusable code.
* Function Composition:
* Function composition is a technique for
combining multiple functions into a single
function.
* This can be used to build complex
operations from simpler ones.

* Referential Transparency:
* A function is referentially transparent if it
always produces the same output for the same
input, regardless of the context in which it is
called.
* This makes code easier to reason about and
test.
Example
Let's consider a simple example in Python:
def add(x, y):
return x + y
def square(x):
return x * x
# Function composition
result = add(square(2), square(3)) # result will
be 13
In this example, square and add are pure
functions. We compose them using function
composition to calculate the result.

LISP(Functional programming language):

Lisp (List Processing) is often hailed as the first


functional programming language. It was
developed in the late 1950s and has had a
profound influence on the development of
programming languages and computer science
in general.
Key Characteristics of Lisp
* Simple Data Structures: Lisp primarily uses
lists as its fundamental data structure. These
lists can be nested to represent complex data
structures.
* Lambda Notation: Lisp employs lambda
notation to define anonymous functions. This
allows for the creation of higher-order
functions, which are functions that take
functions as arguments or return functions as
results.
* Functional Style: Lisp encourages a
functional style of programming, where
computations are performed by applying

functions to arguments. This style promotes


code clarity, modularity, and reusability.
* Homogeneity: In Lisp, code and data share
the same representation, which is often
expressed as S-expressions (symbolic
expressions). This allows for metaprogramming
and the ability to manipulate code as data.
Example: List Structure in Lisp
A list is enclosed in parentheses, and its
elements can be atoms (single symbols) or
nested lists.
For example, the list (A B (C D) E) represents a
list with five elements: the atoms A, B, and E,
and two nested lists (C D) and (F G).
Internal Representation of Lisp Lists
Lisp lists are typically represented internally as
linked lists. Each node in the linked list
contains a pointer to the next

node and a pointer to the data it holds.


Lisp's Legacy
Lisp's influence can be seen in many modern
programming languages It has been used in
various domains, such as artificial intelligence,
symbolic computation, and web development

Support for Functional Programming in


Primarily Imperative Languages:
While imperative languages like C++, Python,
and Java are primarily designed for procedural
programming, they have incorporated features
to support functional programming to some
extent:
1. Higher-Order Functions:
* Many modern imperative languages now
support higher-order functions, allowing
functions to be passed as arguments and
returned as results. This is a fundamental
concept in functional programming.
* the lack of support for higher-order functions
was a significant limitation of imperative
languages in the past.
2. Anonymous Functions (Lambdas):
* These enable defining functions without
explicitly naming them, which is useful for
concise expressions and functional
programming constructs.

* anonymous functions (like lambda


expressions) are now part of languages like
JavaScript, Python, Ruby, and C#.
3. Functional-Style Libraries:
* Some languages offer libraries that provide
functional-style operations like map, filter, and
reduce, making it easier to write functional-
style code within an imperative language.
* Python includes higher-order functions like
filter and map, which use lambda expressions.
Examples
* JavaScript: Named functions are defined
using the function keyword, while anonymous
functions (lambdas) are defined using a more
concise syntax.
* C#: Lambda expressions have a different
syntax than regular functions, often using the
=> operator.

* Python: Lambda expressions define simple


one-statement anonymous functions.
* Ruby: Blocks can be converted to
subprogram objects using lambdas.
In Summary
While imperative languages were traditionally
not well-suited for functional programming,
they have evolved to include features that
support functional programming paradigms.
This allows developers to write more concise,
modular, and reusable code, even within the
context of imperative languages.

Comparison of Functional & Imperative


Languages:
1. Syntax:
Functional languages often have a simpler
syntactic structure compared to the more
complex syntax of imperative languages.
Imperative languages tend to have a more
complex syntax due to their focus on control
flow and state management.
2. Execution:
Functional languages can be less efficient in
execution due to their reliance on function calls
and recursion.
Imperative languages, with their direct
manipulation of memory, are generally more
efficient in execution.

3. Design:
The design of functional languages is based on
mathematical functions, emphasizing the
concept of pure functions that produce the same
output for the same input without any side
effects.
The design of imperative languages is based on
the Von Neumann architecture, which focuses
on sequential execution of instructions and
manipulation of memory.
4. Concurrency:
Concurrency is often easier to achieve in
functional languages due to their lack of side
effects and their ability to automatically
parallelize computations.
Concurrency is more difficult to design in
imperative languages because it requires careful
management of shared state and
synchronization mechanisms to avoid race
conditions and other concurrency issues.
Logic programming language:
a logic programming language is defined as a
declarative programming language. This means
that you specify the desired outcome (what)
rather than the exact procedure to achieve it
(how). In other words, you declare facts and
rules about a problem domain, and the
computer uses logical inference to derive
conclusions from these statements.
Key Characteristics:
* Declarative: Focuses on specifying the
desired outcome rather than the step-by-step
procedure.
* Logical: Based on formal logic, using
symbols and rules to represent knowledge and
reasoning.
* Inference-based: Employs a logical inference
engine to derive new information from existing
facts and rules.

A logical inference engine is a software


component that uses logical rules and data to
derive new information.
Proposition
In the context of logic programming, a
proposition is a declarative statement that is
either true or false. It's a fundamental building
block used to express facts and rules within a
knowledge base
Symbolic Logic in Logic Programming:
In logic programming, symbolic logic is used to
represent knowledge and rules in a declarative
way. The computer then uses a logical inference
engine to derive conclusions based on these
statements. This allows for flexible and
powerful reasoning capabilities.
By understanding symbolic logic, you can
effectively express complex ideas and reason
about them in a precise and formal manner.

Object Representation in Logic


Programming
In logic programming, objects are typically
represented using terms. A term can be a
constant, a variable, or a compound term.
Types of Terms:
* Constant:
* Represents a fixed value, such as a number,
a string, or a symbol.
* Examples: 'john', 42, true
* Variable:
* Represents an unknown value that can be
instantiated during the execution of a program.
* Variables are typically denoted by uppercase
letters.
* Examples: X, Y, Z

Compound Terms
Compound terms are a powerful way to
represent complex objects in logic
programming. They allow you to define
hierarchical structures and relationships
between objects.
Syntax:
functor(argument1, argument2, ...)

Example:
Consider the following compound term:
person(Name, Age, Gender)

This term represents a person with the name


Name, age Age, and gender Gender. You can
use this term to represent specific individuals,
such as:
person('Alice', 30, female)
person('Bob', 25, male
Compound terms are a fundamental building
block in logic programming, representing
structured data with a functor (name) and
arguments.
* Parts:
* Functor: The name of the relationship or
object being described.
* Arguments: The components of the
compound term, often represented as a list of
terms (similar to a tuple).
* Example:
student(john, 20, computer_science)
Here, "student" is the functor, and "john,"
"20," and "computer_science" are the
arguments.
Propositions are declarative statements that can
be either true or false. They are the basic units
of logical reasoning.
* Facts: Propositions assumed to be true.
* Queries: Propositions whose truth value
needs to be determined.

* Compound Propositions: These are formed


by combining atomic propositions using logical
operators.
* Logical Operators:
* Negation (¬): Reverses the truth value of a
proposition.
* Conjunction (∧): Both propositions must be
true.
* Disjunction (∨): At least one proposition
must be true.
* Implication (→): If the first proposition is
true, the second must also be true.
* Equivalence (↔): Both propositions have
the same truth value.
Quantifiers:
* Universal Quantifier (∀): "For all" or "For
every."
* Existential Quantifier (∃): "There exists" or
"For some.”

Predicate Calculus
Predicate calculus, also known as first-order
logic, is a formal system used to represent and
reason about knowledge within a structured
framework. It provides a way to express
complex propositions using variables,
predicates, and quantifiers.
Key Components:
* Predicates: These are symbols that represent
relationships between objects. For example, the
predicate Parent(x, y) could represent the
relationship of x being a parent of y.
* Variables: These are placeholders for objects.
In the Parent(x, y) example, x and y are
variables that can be instantiated with specific
objects.
* Quantifiers: These are symbols that express
the scope of a predicate over a range of objects.
The two main quantifiers are:

* Universal Quantifier (∀): This quantifier


means "for all." For example, ∀x Parent(x, y)
would mean "for all x, x is a parent of y."
* Existential Quantifier (∃): This quantifier
means "there exists." For example, ∃x Parent(x,
y) would mean "there exists an x such that x is
a parent of y."
Theorem Proving
Theorem proving is the process of using logical
reasoning to derive new theorems from a set of
axioms and previously proven theorems. In the
context of predicate calculus, theorem proving
involves creating formal proofs that
demonstrate the validity of a statement within
the logical system.

Resolution
Resolution is a fundamental inference rule used
in theorem proving. It works by combining two
clauses (disjunctions of literals) to derive a new
clause. The two clauses must have
complementary literals, meaning one literal is
the negation of the other.
Example:
Given the following clauses:
* P(x) ∨ ¬Q(x)
* Q(a)
We can apply resolution to derive the new
clause P(a):
1. P(x) ∨ ¬Q(x)
2. Q(a)
------------------
3. P(a)

Unification and Instantiation


Unification is the process of finding values for
variables that make two terms equal.
Instantiation is the process of assigning values
to variables. These processes are crucial in
applying resolution.

Theorem Proving in Prolog


Prolog, a programming language based on
logic, is well-suited for theorem proving. It
allows programmers to express knowledge in a
declarative way, and the resolution principle is
used to derive new facts and conclusions.

Origin of prolog
Prolog originated from research at the
University of Aix-Marseille in the late 1960s
and early 1970s. Alain Colmerauer and Philippe
Roussel, along with Robert Kowalski of the
University of Edinburgh, collaborated to create
the core design of Prolog.
1. Basic Elements of Prolog
* Term: A term is the building block of Prolog
programs. It can be a constant, a variable, or a
structure.
* Constant: Represents a fixed value. It can be
an atom (a symbolic value) or an integer.
* Atoms: Consist of letters, digits, and
underscores.
* Begin with a lowercase letter.
* Variable: Represented by an uppercase letter.
It can take on different values during program
execution.

* Structure: Represents a relationship between


other terms. It consists of a functor (a name)
and a list of arguments (other terms).
2. Fact Statements
* Used to represent atomic propositions.
* Format: head(arguments).
* Example: female(shelley).
* Facts are used to define the basic knowledge
base of a Prolog program.
3. Rule Statements
* Used to define relationships between facts.
* Format: head(arguments) :- body(arguments).
* Example: ancestor(X,Y) :- parent(X,Y).
* The head is the conclusion, and the body is
the condition.
* Rules allow for chaining of facts and other
rules to derive new knowledge.

4. Goal Statements
* Used to query the Prolog database.
* Format: ?- goal(arguments).
* Example: ?- ancestor(bill, jake).
* Prolog's inference engine attempts to prove
the goal by matching it with facts and rules in
the knowledge base.
Example:
Consider the following Prolog program:
parent(john, mary).
parent(john, peter).
parent(mary, sue).
parent(peter, bill).

grandparent(X,Z) :- parent(X,Y), parent(Y,Z).

To find out who is Bill's grandparent, we can


issue the following query:
?- grandparent(X, bill).
Prolog will use the rules and facts to deduce
that John is Bill's grandparent.

1. Inferencing Process in Prolog


* Queries as Goals: In Prolog, queries are
treated as goals to be proven.
* Compound Goals: If a goal is a compound
proposition (e.g., A and B), each part (A and B)
becomes a subgoal.
* Chain of Inference: To prove a goal, Prolog
tries to find a chain of rules and facts that
connects the goal to the known facts. This chain
is essentially a proof.
2. Approaches to Resolution
* Bottom-Up Resolution (Forward Chaining):
* Starts with facts and rules and attempts to
derive new facts until the goal is reached.
Works well when there are a small number of
possible correct answers.

* Top-Down Resolution (Backward Chaining):


Starts with the goal and attempts to find a
sequence of rules and facts that lead to the goal.
* Works well when there are a small number
of possible correct answers.
* Prolog Implementation: Prolog uses
backward chaining as its primary inference
strategy.
3. Subgoal Strategies
* Depth-First Search:
* Focuses on one subgoal at a time, exploring
it completely before moving on to the next.
* Can be efficient but might miss solutions if
there are infinite paths to explore.
* Breadth-First Search:Explores all subgoals at
the same level before moving deeper.
4. Backtracking
* If a subgoal fails, Prolog backtracks to the
previous choice point and tries an alternative
solution.
* This allows Prolog to explore different paths
in the search space.
5. Simple Arithmetic
* Prolog supports integer arithmetic using the is
operator.
* The is operator takes an arithmetic expression
on the right and assigns the result to a variable
on the left.
* It's important to note that this is not an
assignment statement in the traditional sense.

Example:
speed(ford, 100).
speed(chevy, 90).
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.
Explanation:
* Facts: The facts define the speed and time for
different cars.
* Rule: The distance(X, Y) rule calculates the
distance traveled by a car given its speed and
time.
* Arithmetic: The is operator is used to
calculate the distance by multiplying the

speed and time.


1. Trace
* Built-in Structure: Prolog provides a built-in
structure called "trace" that displays the
instantiations of variables at each step of
execution.
* Tracing Model of Execution: The trace
mechanism follows four events:
* Call: When an attempt is made to satisfy a
goal.
* Exit: When a goal has been successfully
satisfied.
* Redo: When backtracking occurs to try an
alternative solution.
* Fail: When a goal fails to be satisfied.
2. List Structures
Lists are a fundamental data structure in Prolog.
They represent a sequence of elements.
*

Elements: List elements can be atoms, atomic


propositions, or other terms, including other
lists.
* Syntax:
* []: Represents an empty list.
* [Head|Tail]: Represents a list with a head
element (Head) and a tail (Tail), which is
another list.
3. Append Operation
* Purpose: The append predicate is used to
concatenate two lists.
* Syntax: append(List1, List2, List3)
* List1: The first list.
* List2: The second list.
* List3: The concatenated list.
* Example:
append([], List, List).
append([Head|Tail], List2, [Head|List3]) :-
append(Tail, List2, List3).
4. Reverse Operation
* Purpose: The reverse predicate is used to
reverse the order of elements in a list.
* Syntax: reverse(List, ReversedList)
* List: The input list.
* ReversedList: The reversed list.
* Example:
reverse([], []).
reverse([Head|Tail], ReversedList) :-
reverse(Tail, ReversedList1),
append(ReversedList1, [Head],
ReversedList).
5. Deficiencies of Prolog
* Resolution Order: Prolog's inference engine
can be inefficient due to its fixed resolution
order.
* Closed-World Assumption: Prolog assumes
that everything that is not explicitly known to
be true is false. This can lead to incorrect
conclusions in some cases.

* Negation Problem: Negation in Prolog can be


problematic, especially when dealing with
incomplete information.
6. Applications of Logic Programming
* Relational Database Management Systems:
Prolog can be used to query and manipulate
relational databases.
* Expert Systems: Prolog can be used to
implement expert systems that can reason
and make decisions based on knowledge and
rules.

Meta-Language
In the context of programming languages, a
meta-language is a language used to describe or
analyze another language. It's essentially a
language that talks about languages.
Here are some key aspects of meta-languages:
1. Static Scoped Functional Language:
* This means that the scope of variables is
determined at compile time, not at runtime.
* It is a functional language, emphasizing the
use of functions as the primary building blocks.
* The syntax is closer to Pascal than LISP,
suggesting a more structured and less
parenthetical approach.
2. Type Declarations and Inference:
* The language allows for explicit type
declarations, where you specify the type of
a variable or function.
* It also supports type inference, where the
compiler can automatically deduce the type of a
variable based on its usage.
* The language is strongly typed, meaning that
variables have fixed types, and type
mismatches are caught at compile time.
3. Exception Handling and Module Facility:
* The language includes mechanisms for
handling exceptions, which are errors that occur
during program execution.
* It has a module facility, allowing for the
creation of reusable code units with well-
defined interfaces.
4. Lists and List Operations:
* The language supports lists, which are
ordered collections of elements.
* It provides operations for creating,
manipulating, and traversing lists.
5. Function Declaration and Pattern Matching:

* Functions are declared using a syntax similar


to Pascal, with parameters and a body.
* Pattern matching allows functions to operate
on different parameter forms, making code
more concise and expressive.
6. Type Coercions:
* The language typically does not perform
automatic type conversions (coercions). This
means that you need to be explicit about type
conversions
the meta-language specifies
Syntax:
* Function declaration: The syntax for
declaring functions, including the function
name, parameters, and body.
Function Declarations in ML
In ML, functions are defined using the fun
keyword.

The syntax for a simple function declaration is:


fun function_name(parameter1, parameter2, ...)
= expression

Example:
fun add(x, y) = x + y;
This defines a function named add that takes
two integer parameters, x and y, and returns
their sum.
Function Application:
To call a function, you simply write the
function name followed by the arguments in
parentheses:
val result = add(3, 4);

This will assign the value 7 to the variable


result

Pattern matching: The syntax for defining


functions that can operate on different input
patterns.
Pattern Matching in Function Definitions
ML supports pattern matching in function
definitions, allowing for more concise and
expressive code. Here's an example:
fun factorial(0) = 1
| factorial(n) = n * factorial(n - 1);
This defines a recursive function factorial that
calculates the factorial of a non-negative
integer. The function has two clauses:
* Base case: When the input n is 0, the function
returns 1.
* Recursive case: When n is greater than 0, the
function recursively calls itself with n - 1 and
multiplies the result by n.
Currying

ML Selection
ML Selection likely refers to a mechanism for
selecting elements from a list based on certain
conditions. While the exact details might vary
depending on the specific language
implementation,
List Selection:
Lists in functional programming languages are
often represented as linked lists. Each list
element consists of two parts:
* Head: The first element of the list.
* Tail: The rest of the list.
Selection Operations:
* Head: Extracts the first element of a list.
* Tail: Extracts the rest of the list, excluding the
first element.
* Pattern Matching: A powerful technique to
select elements based on specific patterns. For
example, you can match a list against patterns
like [x, y, z] or [x, y | rest].

Example:
let list = [1, 2, 3, 4, 5];
// Selecting the head
let head = hd list; // head = 1

// Selecting the tail


let tail = tl list; // tail = [2, 3, 4, 5]

// Selecting elements using pattern matching


let [x, y, z] = [10, 20, 30]; // x = 10, y = 20, z =
30

Haskell
Haskell is a purely functional programming
language, which means it avoids side effects
and mutable state. It's known for its strong type
system, pattern matching, and lazy evaluation.

Similarities to ML
* Static Scoping: The scope of variables is
determined at compile time.
* Strong Typing: Variables have fixed types,
and the compiler enforces type safety.
* Type Inference: The compiler can often infer
the types of variables without explicit
declarations.
* Pattern Matching: A powerful technique for
matching values against patterns.

Differences from ML
* Purely Functional: Haskell avoids side effects
and mutable state, making it more
mathematically rigorous and easier to reason
about.
* No Variables: Haskell doesn't have mutable
variables. Values are assigned to immutable
bindings.
* No Assignment Statements: There are no
statements like x = y in Haskell.
* No Side Effects: Functions in Haskell cannot
change the state of the program outside their
local scope.
Function Definitions
* Recursive Functions: Functions can be
defined recursively, as shown in the examples
for factorial and fib.
* Pattern Matching: Functions can have
multiple definitions, each matching a different
pattern.
* Guard Expressions: where clauses can be
used to define additional conditions for function
definitions.
Lists
* List Notation: Lists are defined using square
brackets [ ].
* List Operations: Haskell provides various
operations for working with lists, such as head,
tail, concat, and list comprehensions.
* List Comprehensions: A concise way to create
lists by filtering and transforming elements
from other lists.
Example:
-- Factorial using list comprehension and
product
factorial n = product [1..n]

-- List comprehension to generate squares of


numbers from 1 to 20
squares = [x*x | x <- [1..20]]

Lazy Evaluation in Haskell


Lazy Evaluation is a strategy where expressions
are evaluated only when their values are
needed. This can lead to significant
performance improvements and enables the
creation of infinite data structures.
* Infinite Lists: Haskell can define infinite lists,
like [1..] which represents the list of all positive
integers.
* Computing Only Necessary Values: Lazy
evaluation ensures that only the required
elements of the infinite list are computed,
avoiding unnecessary calculations.
* Determining if a Number is a Square: The
member function can be used to check if a
number is a member of a list. In the case of
infinite lists, lazy evaluation allows us to check
membership without computing the entire list.

Example: Determining if 16 is a Square


Number
squares = [x*x | x <- [1..]]
member (x:xs) y = x == y || member xs y

* squares defines an infinite list of squares.


* member checks if y is in the list x:xs. It first
compares x with y. If they are equal, it returns
True. Otherwise, it recursively checks the rest
of the list xs.
When we call member squares 16, the squares
list is not fully evaluated. Only the necessary
elements are computed as the member function
recurses.
In the context of Haskell, "Member Revisited"
likely refers to an improved or alternative
implementation of a function that checks for
membership within a list.

The Original member Function:


The original member function, as discussed
earlier, might have been less efficient or less
clear. It might have involved unnecessary
recursion or redundant checks.
The Revisited member2 Function:
The "revisited" version, member2, is often a
more optimized or elegant implementation. It
might have the following improvements:
* Tail Recursion: It might be tail-recursive,
making it more efficient for the compiler to
optimize.
* Pattern Matching: It might use pattern
matching more effectively to handle different
cases.
* Early Termination: It might have conditions
to terminate the recursion earlier, avoiding
unnecessary checks.
Example:

-- Original (less efficient)


member x [] = False
member x (y:ys) = x == y || member x ys

-- Revisited (more efficient)


member2 [] x = False
member2 (y:ys) x = x == y || member2 ys x

In this example, member2 is a tail-recursive


implementation, which is generally more
efficient than the original member function.

Scripting Languages
* Definition: A scripting language is a
programming language designed to glue
together different software components or
subsystems.
* Key Characteristics:
* Rapid development and evaluation of
scripts.
* Modest efficiency requirements.
* Very high-level functionality in specific
application areas.
* Very high-level string processing
capabilities.
* Very high-level graphical user interface
support.
* Dynamic typing (variables don't need to be
declared with a specific data type).
Case Study: Python
* Origin: Python was created by Guido van
Rossum in the early 1990s.

* Applications: Python has been used in a wide


range of applications, including web
development, data science, machine learning,
and system administration.
* Values and Types:
* Python has a limited set of primitive data
types: integers, floating-point numbers,
complex numbers, and boolean values (True
and False).
* Strings are used to represent character
sequences.
* Python has rich composite data types:
* Tuples: Ordered, immutable sequences of
values.
* Lists: Ordered, mutable sequences of
values.
* Dictionaries: Unordered key-value
mappings.

Example Code:
# Tuple construction
date = (1998, "Nov", 19)
# List construction
list1 = [1, 2, 3]
list2 = ["apple", "banana", "cherry"]
Python.
Variables, Storage, and Control
* Variables:
* Python supports both global and local
variables.
* Variables are not explicitly declared, they
are initialized by assignment.
* A variable can hold any data type, and its
type can change during program execution.

* Control Flow:
* Python uses standard control flow
commands like:
* Assignment
* Procedure calls
* Conditional statements (if, else, elif)
* Iterative statements (while, for)
* Exception handling
* Simultaneous Assignment: Python allows
simultaneous assignment of multiple variables
in a single line using tuples. For example:
x, y = 10, 20

This assigns 10 to x and 20 to y.


* Swapping Values: To swap the values of two
variables, you can use simultaneous
assignment:
a, b = b, a

* Definite Iteration: Python provides the


range() function to iterate over a sequence of
numbers. For example:
for i in range(5):
print(i)

This will print numbers from 0 to 4.


* Indentation: Python uses indentation to
define code blocks, making it easy to read and
understand.
Example: Calculating the Greatest Common
Divisor (GCD)
def gcd(m, n):
p, q = m, n
while p % q != 0:
p, q = q, p % q
return q

Explanation:
* The gcd function takes two integers m and n
as input.
* It initializes two variables p and q with the
values of m and n, respectively.
* The while loop continues as long as p is not
divisible by q.
* Inside the loop, the values of p and q are
swapped using simultaneous assignment.
* The loop terminates when p is divisible by q,
and the value of q is returned as the GCD.
Python Exceptions
The code snippet demonstrates how to handle
potential errors using try and except blocks.
Here's the breakdown:
while True:
try:
response = input("Enter a numeric literal:
")
num = float(response)
break
except ValueError:
print("Your response was ill-formed.")

* try Block: This block contains the code that


might raise an exception. Here, we attempt to
convert the user input to a floating-point
number using float(response).
* except Block: If the conversion fails, a
ValueError exception is raised, and control is
transferred to this block. The code within this
block handles the error by printing an error
message.

* while True Loop: This loop keeps prompting


the user for input until a valid numeric literal is
entered.
Bindings and Scope
* Module Structure: A Python program consists
of modules, which can be further organized into
packages.
* Variable Scope:
* Global Variables: Variables defined at the
module level are accessible throughout the
module.
* Local Variables: Variables defined within a
function or method are only accessible within
that function or method.
* Class Variables: Variables defined within a
class are accessible to all instances of that class.
* Instance Variables: Variables defined within
an instance of a class are specific to that
instance.

* Interactive Sessions: In an interactive Python


session, you can directly execute expressions
and statements. Variables defined in this way
are typically global.
* Dynamic vs. Static Scoping: Python
originally used dynamic scoping but has since
transitioned to static scoping, which is more
common in modern programming languages.
Procedural Abstraction
* Functions and Procedures: Python supports
functions and procedures, which are blocks of
code that perform a specific task.
* Code Reusability: Functions and procedures
promote code reusability by encapsulating
common operations.
* Modular Design: By breaking down a
complex problem into smaller functions, you
can create more modular and maintainable
code.

Example:
def greet(name):
print("Hello, " + name + "!")

greet("Alice")

This function takes a name as input and prints a


greeting message.
Python Classes
* Class Definition: The image shows the
definition of a Person class with several
instance methods.
* Instance Variables: The __init__ method
initializes the following instance variables:
* __surname
* __forename
* __female
* __birth
*

Instance Methods:
* get_surname(): Returns the person's
surname.
* change_surname(): Changes the person's
surname.
* print_details(): Prints the person's full name.
Inheritance
* Multiple Inheritance: Python supports
multiple inheritance, meaning a class can
inherit from multiple parent classes.
* Ambiguous References: If a class inherits
from multiple classes that define the same
method or attribute, Python resolves the
ambiguity by searching the parent classes in the
order they are listed in the class declaration.

Privacy
* Naming Convention: Python uses a naming
convention to indicate privacy: attributes and
methods with double underscores (__) at the
beginning are considered private.
* Private Members: While the __ naming
convention suggests privacy, it's not strictly
enforced. Python doesn't prevent access to these
members, but it makes it less likely that they
will be accessed or modified outside the class.
* Public by Default: In Python, class members
are public by default, meaning they can be
accessed and modified from anywhere.
Separate Compilation
* Module Compilation: Python modules are
compiled separately.
* Explicit Import: Each module must explicitly
import any other module

Key Points:
* Python's object-oriented programming
features provide a way to structure code and
model real-world objects.
* The __ naming convention is a convention,
not a strict enforcement of privacy.
* Multiple inheritance can be powerful but can
also lead to complex inheritance hierarchies.
* Separate compilation allows for modularity
and code reuse.
Separate Compilation
* Module Independence: Python modules are
compiled independently. Each module is stored
in a separate text file.
* Compilation Process: When a module is
imported for the first time, it is compiled into
bytecode and stored in a .pyc file. Subsequent
imports use the compiled bytecode, which is
faster to load.

* Automatic Recompilation: If the source code


of a module is modified, the module is
automatically recompiled the next time it is
imported.
* Dynamic Typing: Python's dynamic typing
allows for flexibility, but it can also lead to
runtime errors if variables are used with
incompatible types.
Module Library
* Rich Standard Library: Python comes with a
comprehensive standard library that includes
modules for various tasks, such as string
manipulation, regular expressions, network
programming, file I/O, and more.
* Third-Party Libraries: Python also has a vast
ecosystem of third-party libraries that provide
additional functionality, such as data science,
machine learning, web development, and game
development.

You might also like