0% found this document useful (0 votes)
20 views32 pages

Functional Programmming

Uploaded by

Vishal Ror
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)
20 views32 pages

Functional Programmming

Uploaded by

Vishal Ror
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/ 32

Functional Programming

Functions as First-Class Objects


•In computer science, a first-class object is any entity that can be manipulated without
restrictions, meaning it can be assigned to variables, passed as arguments to other
functions, and returned from other functions.
•First-class functions (or functions as first-class objects) extend this concept to
functions themselves, treating functions as values or objects that can be used flexibly
within a programming language.
•When functions are first-class, they can be stored in variables, used as arguments,
and even returned from other functions.
•This capability is foundational to functional programming, but many modern
programming languages, including those that are traditionally object-oriented,
support this concept to varying degrees.
Functional Programming
Key Benefits of First-Class Functions
1. Code Reusability: First-class functions can be reused in multiple contexts by
passing them as arguments, which enhances modularity and flexibility.

2. Higher-Order Functions: Functions that take other functions as arguments or


return them as results are possible with first-class functions.

3. Functional Composition and Abstraction: First-class functions enable


powerful design patterns, like map-reduce and callback functions.
Implementing Functions as First-Class
Objects in Different Languages
C++
C++ did not treat functions as first-class objects in the same way as languages like Python or JavaScript.
However, with the addition of function pointers, function objects (functors), and, in modern C++,
std::function and lambda expressions, C++ has gained more flexible ways to treat functions as first-class
citizens.
• Function Pointers: C++ has long supported function pointers, which allow functions to be passed
around similarly to first-class functions.
Implementing Functions as First-Class
Objects in Different Languages
• std::function and Lambdas: C++11 introduced std::function and lambda expressions, which made it easier to
treat functions as first-class objects.

• This code demonstrates the flexibility of std::function and lambdas in treating functions as values, making it
possible to pass them around and use them as first-class objects in modern C++.
Implementing Functions as First-Class
Objects in Different Languages
Java
In earlier versions of Java, functions could not be directly treated as first-class objects. However, with the
introduction of lambdas and functional interfaces in Java 8, Java gained the ability to treat functions as first-class
entities to some extent.
• Functional Interfaces and Lambdas: A functional interface is an interface with a single abstract method, which can
be implemented with a lambda expression. By using functional interfaces, Java can pass functions as arguments
and store them in variables.

In this example, BiFunction is a


functional interface that
represents a function taking two
arguments and returning a result.
This allows Java to treat lambdas
(and thus functions) as first-class
objects, enhancing code flexibility.
Implementing Functions as First-Class
Objects in Different Languages
Prolog
Prolog is a logic programming language where functions are not typically treated as first-class objects in the
traditional sense. However, Prolog does support a similar concept by treating predicates as "goals" that can be
manipulated or called within other predicates.
• Prolog’s handling of predicates is somewhat similar to higher-order functions, although it doesn’t directly support
passing functions as arguments in the same way as C++ or Java. Prolog allows predicates to be dynamically
defined, meaning they can be queried or called within other predicates, which provides a form of functional
abstraction.

In this code, execute_add calls


add, allowing Prolog to simulate
the use of one predicate within
another. Though Prolog lacks first-
class functions in a strict sense,
predicates and goals serve a
similar role in functional
decomposition.
Higher-Order Function

Higher-order functions (HOFs) are a core concept in functional programming. A


function is considered a higher-order function if it either:
1. Takes one or more functions as arguments or,
2. Returns a function as its result.

Higher-order functions allow functions to operate on other functions, which facilitates


code modularity, reusability, and flexibility. By using higher-order functions, we can
create powerful abstractions and compose functions in a way that enhances code
expressiveness. Many common functional programming techniques, such as map,
filter, and reduce, rely on higher-order functions.
Functional Programming
Benefits of Higher-Order Functions
1. Modularity and Code Reuse: Higher-order functions allow us to define
general-purpose operations that can be reused across different parts of the
code.
2. Abstraction: HOFs enable us to abstract away control flows, such as
iteration, and focus on the core operations.
3. Functional Composition: HOFs make it possible to compose small functions
into more complex operations, which is ideal for creating pipelines or chains
of transformations.
Higher-Order Functions in Different Languages
C++
In C++, higher-order functions can be implemented using function pointers, std::function, and lambda
expressions. Starting with C++11, the use of std::function and lambdas has made it easier to define and
pass functions as parameters.

In this example, applyToAll is a


higher-order function that
accepts a vector and a function
as arguments. We pass a lambda
function to double each element,
which makes this function
adaptable to different operations
without changing its structure.
Higher-Order Functions in Different Languages
Java
Java introduced lambdas and functional interfaces in Java 8, which allowed for higher-order functions.
Functional interfaces (interfaces with a single abstract method) enable us to pass functions as arguments,
which are then used within the higher-order function.

Here, applyToAll is a higher-order


function that accepts a list and a
Consumer functional interface as
parameters. The Consumer interface
is implemented with a lambda
function to double each value. This
code illustrates how higher-order
functions make operations like
doubling flexible and reusable
without modifying the core
applyToAll function.
Higher-Order Functions in Different Languages
Prolog
In Prolog, higher-order functions are not available in the same way as in imperative languages. However,
Prolog does provide meta-predicates, which are predicates that take other predicates as arguments. This
allows Prolog to mimic some higher-order functionality.

In this example, apply_to_all takes a


predicate (double) and a list as
arguments. The call predicate allows
Prolog to invoke the passed
predicate dynamically. The predicate
apply_to_all can then apply double
to each element in the list,
producing a new list with doubled
values. Though Prolog doesn’t
support higher-order functions in
the strict sense, this approach
allows for similar flexibility with
predicates.
Real-World Examples of Higher-
Order Functions
Higher-order functions are widely used in programming, especially in data
processing and manipulation:

1. Map: Takes a function and a list, applies the function to each item in the list,
and returns a new list with the results.
• Example: Doubling each item in a list.
2. Filter: Takes a predicate (a function that returns true or false) and a list, and
returns a new list with only the items that satisfy the predicate.
• Example: Filtering out even numbers from a list.
3. Reduce (or Fold): Takes a function, a list, and an initial accumulator value,
and combines the elements of the list using the function in a way defined by
the function itself.
• Example: Calculating the sum of a list of numbers.
Polymorphic Data Types

Polymorphic data types allow for the creation of structures, functions, and types that
can operate on multiple data types without modification. In simpler terms,
polymorphism in programming means "many forms," allowing entities like variables or
functions to handle different types of data through a unified interface. This is crucial
for building flexible, reusable, and generic code.

Polymorphism is especially powerful in functional programming and languages with


robust type systems. Parametric polymorphism (or generic programming) is a
common type of polymorphism, enabling functions or data structures to be written
generically so that they work with any data type, thus providing both type safety and
flexibility
Polymorphic Data Types
Types of Polymorphism
1. Parametric Polymorphism (Generic Programming): A function or data structure
can take on multiple types based on parameters. For example, a function that
can accept any data type for its input without knowing the exact type is called a
generic function.
2. Ad-hoc Polymorphism (Overloading): Different versions of a function or data
structure are created for different types. This includes function overloading and
operator overloading, where multiple functions or operators share the same
name but differ based on the types they accept.
3. Subtype Polymorphism (Inheritance-based): A function can work with any type
that is a subclass or subtype of a given parent class. This is commonly used in
object-oriented programming.
Polymorphic Data Types
Advantages of Polymorphic Data Types

1. Code Reusability: By making functions and data structures generic, they can be
reused across various contexts without rewriting them for each data type.
2. Abstraction: Polymorphism abstracts away specifics about data types, letting
programmers focus on the structure and behavior of the code rather than
specific implementations.
3. Type Safety: Unlike using a universal type (like void* in C), polymorphic data
types maintain type safety, reducing the risk of runtime errors.
4. Flexibility: With polymorphic data types, functions can handle multiple types
with minimal code changes, making the code more adaptable and concise.
Polymorphic Data Types
C++
C++ supports polymorphic data types through templates, which allow functions and classes to operate on
any data type specified at compile-time. Templates enable type-safe generic programming, making them a
powerful tool for creating reusable code.
Example of Parametric Polymorphism with Templates in C++:

In this example, add is a


polymorphic function that can
accept arguments of any type T,
as long as the + operator is
defined for that type. The
template parameter T allows the
function to work for both int and
double inputs without separate
implementations.
C++ templates can also create
polymorphic data structures, like
a stack or a linked list that can
store any data type.

Here, Stack is a polymorphic data


structure that can hold any data
type specified. By using
templates, C++ enables us to
define a generic stack that can
store int, string, or any other
type.
Polymorphic Data Types
Java
Java supports polymorphism through generics, introduced in Java 5. Generics allow Java to enforce type
safety while enabling flexible, reusable code that works with multiple data types.

Example of Parametric Polymorphism with Generics in Java

In this example, the add method


uses a type parameter <T> to
accept any data type, though it
requires type-specific handling
due to Java’s type erasure (i.e., it
lacks type information at
runtime).
Generic Classes in Java:

Here, Box is a generic class in


Java that can store any data type.
By using type parameters (<T>),
Java enforces type safety while
enabling flexibility.
Polymorphic Data Types
Prolog
Prolog does not have a native concept of polymorphic data types or generics as seen in C++ or Java.
However, Prolog predicates inherently work with multiple data types because of its dynamic typing.
Prolog’s predicates are polymorphic by nature, as they can accept various types (integers, atoms, lists)
without requiring explicit type declarations.

Here, print_item can accept


integers, strings, lists, and other
Prolog data types without
modification. Prolog’s dynamically
typed nature allows it to handle
polymorphic data without explicit
generics or templates.
Polymorphic Data Types
Haskell
Haskell, as a purely functional language, embraces polymorphism, especially parametric polymorphism,
using type variables that make functions and data structures inherently generic.

In this example, identity is a


polymorphic function that works
with any type, denoted by the type
variable a. The function takes a
parameter of any type and returns it
as-is. Haskell’s type inference system
deduces the type of identity based
on its usage, making it highly flexible
and type-safe.
Real-World Examples of Polymorphic Data Types
Polymorphic data types are commonly used in data structures and utility functions:

1. Data Structures: Generic collections, such as lists, arrays, and maps, are commonly
implemented with polymorphism, allowing them to store any data type.
• For example, a List<T> in Java can hold any type specified at runtime, such as List<Integer>,
List<String>, etc.
2. Utility Functions: Many utility functions leverage polymorphism to perform general
operations.
• A function like filter in functional programming can take a list of any type and return a list of
the same type after applying a predicate.
3. Standard Libraries: Standard libraries in most programming languages are built with
polymorphism to accommodate a wide range of operations. For instance, the
std::vector<T> in C++ can store any type T, while providing a consistent interface for
various operations.
Type Checking and Type Inference
Type checking and type inference are two critical concepts in programming language
theory that relate to ensuring type safety and making code simpler and more
expressive. These concepts play a fundamental role in managing data types in
programming languages, especially those that emphasize type safety, such as
statically-typed languages (e.g., C++, Java, and Haskell).
• Type Checking: The process of verifying the types of expressions, variables, and
operations within a program to ensure that they are used correctly.
• Type Inference: The process of automatically determining the types of expressions,
variables, and functions, which reduces the need for explicit type annotations.
These concepts contribute to program reliability and robustness, helping detect type
errors at compile time (in statically-typed languages) or runtime (in dynamically-typed
languages).
Type Checking
Type checking is the process by which a programming language verifies that operations are
applied to compatible types. This can occur at compile time or runtime:
• Static Type Checking: Performed at compile time, it catches type errors before the program
is run. Languages like C++, Java, and Haskell use static type checking.
• Dynamic Type Checking: Performed at runtime, dynamic type checking only detects type
errors while the program is running. Languages like Python and JavaScript use dynamic
type checking.

Types of Type Checking Errors


Type checking aims to prevent several common errors, such as:
• Type Mismatch: When an operation is applied to incompatible types, like adding a string to
an integer.
• Undefined Variable Errors: When a variable is used without being declared or initialized.
• Parameter Type Mismatch: When a function is called with arguments of incorrect types.
Example of Type Checking in Different Languages

C++ (Static Type Checking)


• In C++, types must be explicitly declared, and type checking occurs at compile time:

The commented-out line would produce a compile-time error in C++ because num is an integer, and
text is a string. Static type checking ensures that such errors are caught early.
Example of Type Checking in Different Languages

Python (Dynamic Type Checking)


In Python, type checking happens at runtime, as Python is a dynamically-typed language:

Here, the error occurs only when the program is run. Dynamic type checking is more flexible but
can lead to runtime errors if type mismatches are not properly handled.
Type Inference
Type inference is the ability of a programming language to automatically deduce the types of
expressions, variables, and function parameters based on context, without requiring explicit type
annotations. Type inference improves code readability and reduces redundancy, while still providing
the benefits of type checking.
Languages like Haskell, Scala, Kotlin, and modern C++ and Java (using auto and var, respectively)
support type inference to varying degrees. By inferring types, these languages simplify syntax while
maintaining the benefits of type safety.

How Type Inference Works


Type inference usually follows these steps:
• Analyzing Context: The compiler examines the context in which a variable or expression is used.
• Applying Constraints: The compiler applies constraints based on operations and the surrounding
code.
• Determining the Most Specific Type: Based on context and constraints, the compiler deduces the
most specific and compatible type.
Haskell (Complete Type Inference)
Haskell is a strongly typed language that infers types for almost all expressions, including variables and
function parameters:

In this example, Haskell infers the type of square as Num a => a -> a, meaning it can accept any type that is an
instance of the Num class (like Int or Float). The programmer does not need to specify the type explicitly, which
makes the code cleaner.
Java (Local Type Inference with var)
In Java 10 and later, the var keyword enables local type inference, allowing types to be omitted in
variable declarations::

In this example, Java infers the type of message as String and number as int. While Java requires explicit types for
method signatures and fields, var is helpful for simplifying local variable declarations.
C++ (Type Inference with auto)
Modern C++ allows type inference using the auto keyword, which instructs the compiler to deduce the
type of a variable:

In this example, auto tells the compiler to infer num as an int and text as const char*, which simplifies the code
without sacrificing type safety.
Advantages of Type Checking and Type Inference
Advantages of Type Checking
1. Error Detection: Static type checking catches errors at compile-time, making code
more robust.
2. Optimization: Known types help compilers optimize code for better performance.
3. Documentation: Explicit types often serve as documentation, making code easier
to understand.

Advantages of Type Inference


1. Conciseness: Code is cleaner and less cluttered, as type annotations are not
always necessary.
2. Flexibility: Functions and variables are more flexible, as the inferred types adapt
to context.
3. Readability: Reduced need for explicit type declarations improves readability.
Real-World Applications of Type
Checking and Type Inference
1. Generic Libraries: Type inference is particularly useful in libraries like Java’s
Streams API, where functions like map and filter operate on various data types
without requiring explicit type annotations.
2. Data Science and Machine Learning: In dynamically typed languages like
Python, libraries (such as TensorFlow) perform type checking at runtime to
ensure tensor operations are compatible.
3. Web Development: TypeScript, a superset of JavaScript, brings static type
checking to JavaScript, preventing runtime errors in large-scale web
applications by enforcing type safety during development.

You might also like