Programming Techniques - robbiehume/CS-Notes GitHub Wiki
- Colab notebook advanced topic notes
- Python Functions: Lambdas, Closures, Decorators, and Currying
- Good overview of some of the topics
-
Anonymous Functions (General):
-
Key Feature: Functions without a name, used in many languages for short-term use (e.g., JavaScript, Python, Java, etc.)
- Often used as arguments to higher-order functions
-
Key Feature: Functions without a name, used in many languages for short-term use (e.g., JavaScript, Python, Java, etc.)
-
Arrow functions (JavaScript):
-
Syntax:
(params) => { expression }
-
Key feature: inherits
this
from the surrounding scope in which it was defined- This differs from traditional functions, where the value of
this
depends on how the function is called instead of how it's defined
- This differs from traditional functions, where the value of
-
Example:
const add = (a, b) => a + b;
-
Syntax:
-
Lambda functions (Python):
-
Syntax:
lambda <arg(s)>: expression
- Key feature: single-expression anonymous functions
-
Example:
add = lambda a, b: a + b
-
Syntax:
-
Lambda expressions (Java, etc.):
-
Syntax:
(params) -> expression
or(params) -> { statements }
- Key feature: introduced in Java 8, often used in functional interfaces
-
Example:
(a, b) -> a + b
-
Syntax:
First-class functions vs Higher-order functions (link)
- Comparison
- Languages that have first class functions: Javascript, Python, PHP, Ruby, etc.
- Non-functional programming languages such as C++, C and Java have limited support or no support for first class functions
-
First-class functions are functions that are treated like an object (or are assignable to a variable)
- When you say that a language has first-class functions, that means that the language treats functions as values – that you can assign a function to a variable, pass it around, etc.
-
Higher-order functions are functions that take at least one first-class function as a parameter, or return at least one first-class function
- Types of higher-order functions:
map()
,filter()
,reduce()
- Types of higher-order functions:
- Example with first class and higher order functions:
def add(x, y): return x + y def subtract(x, y): return x - y # A higher order function to use the above functions def compute(operation, x, y): return operation(x, y) # Now you can call the 'compute' function with any operation you want result = compute(add, 2, 3) print(result) # prints: 5 result = compute(subtract, 7, 3) print(result) # prints: 4
- .filter() / .map() / .reduce() article
-
filter()
:- Purpose: to create a new collection by including only the elements that satisfy a specific condition.
- How it works: you provide a function (predicate) that returns true or false for each element in the collection. Only the elements that return true will be included in the new collection
-
map()
:- Purpose: to create a new collection by transforming each element in the original collection.
- How it works: you provide a function that applies some operation to each element, and the result of this function is used in the new collection
-
reduce()
:- Purpose: to reduce a collection of elements into a single value by applying a function.
- How it works: you provide a function that accumulates a result by processing each element in the collection. The function takes two arguments: an accumulator and the current element, and returns the updated accumulator
-
const numbers = [1, 2, 3, 4]; const evenNumbers = numbers.filter(num => num % 2 === 0); // [2, 4] const doubled = numbers.map(num => num * 2); // [2, 4, 6, 8] const sum = numbers.reduce((acc, num) => acc + num, 0); // 10
-
List<Integer> numbers = Arrays.asList(1, 2, 3, 4); List<Integer> evenNumbers = numbers.stream() .filter(num -> num % 2 == 0) // keep only even numbers .collect(Collectors.toList()); // [2, 4] List<Integer> evenNumbers = numbers.stream() .map(num -> num * 2) // double each number .collect(Collectors.toList()); // [2, 4, 6, 8] int sum = numbers.stream() .reduce(0, (accumulator, current) -> accumulator + current); // 10
- Like JavaScript, Java also supports chaining:
-
int result = numbers.stream() .filter(num -> num % 2 == 0) // filter even numbers: [2, 4] .map(num -> num * 2) // double the values: [4, 8] .reduce(0, Integer::sum); // sum them up: 4 + 8 = 12
-
-
List comprehensions / generator expressions are often faster than
filter()
/map()
/reduce()
combinations-
Example:
[x * 2 for x in numbers if x % 2 == 0]
is faster thanlist(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, numbers)))
-
Example:
Closures (link)
- A closure is an inner function that remembers and has access to variables in the local scope in which it was created, even after the outer function has finished executing
- The use of closures is associated with languages where functions are first-class objects, in which functions can be returned as results from higher-order functions, or passed as arguments to other function calls
- A decorator is just a function that takes another function as an argument, adds some kind of functionality, and then returns another function
- All of this without altering the source code of the original function that was passed in
- In essence, decorators are a kind of higher-order function
- A wrapper is something that wraps a function, usually adding some extra behavior to it
- A decorator is a type of wrapper; an adaptor is another type of wrapper
-
Currying refers to creating new functions from existing functions by applying partial arguments
- Thus, this concept is sometimes also termed partial functions
- In simple terms, Currying is used to transform a multiple-argument function into a single-argument function by evaluating incremental nesting of function arguments
- The purpose of function currying is to easily get specialized functions from more general functions
- Generators allow us to look at one value at a time and to not store the entire sequence of numbers when it's not necessary to