# 70. Function Currying

**1. Basic Function Currying**

Currying a function to create specialized versions with some arguments pre-filled.

```python
def multiply(a, b):
    return a * b

def curry_multiply(a):
    return lambda b: multiply(a, b)

multiply_by_2 = curry_multiply(2)
print(multiply_by_2(5))  # Output: 10
```

***

**2. Currying with Multiple Arguments**

Currying a function that takes multiple arguments.

```python
def add(a, b, c):
    return a + b + c

def curry_add(a):
    return lambda b, c: add(a, b, c)

add_5 = curry_add(5)
print(add_5(3, 2))  # Output: 10
```

***

**3. Partial Function Using functools.partial**

Using `functools.partial` to implement currying.

```python
from functools import partial

def power(base, exponent):
    return base ** exponent

cube = partial(power, exponent=3)
print(cube(2))  # Output: 8
```

***

**4. Currying with Default Arguments**

Using currying with default values for some arguments.

```python
def greet(greeting, name="Guest"):
    return f"{greeting}, {name}!"

def curry_greet(greeting):
    return lambda name="Guest": greet(greeting, name)

say_hello = curry_greet("Hello")
print(say_hello("Alice"))  # Output: Hello, Alice!
```

***

**5. Currying with Variable Number of Arguments**

Currying a function that accepts a variable number of arguments.

```python
def sum_numbers(*args):
    return sum(args)

def curry_sum(arg1):
    return lambda *args: sum_numbers(arg1, *args)

add_10 = curry_sum(10)
print(add_10(5, 7))  # Output: 22
```

***

**6. Currying for Logging Functions**

Using currying to create specialized logging functions.

```python
def log_message(level, message):
    return f"[{level}] {message}"

def curry_log(level):
    return lambda message: log_message(level, message)

info_log = curry_log("INFO")
print(info_log("System started"))  # Output: [INFO] System started
```

***

**7. Currying with Math Operations**

Creating specialized functions for common mathematical operations.

```python
def subtract(a, b):
    return a - b

def curry_subtract(a):
    return lambda b: subtract(a, b)

subtract_from_10 = curry_subtract(10)
print(subtract_from_10(3))  # Output: 7
```

***

**8. Currying for Database Queries**

Currying a function that generates database queries.

```python
def query(table, columns):
    return f"SELECT {columns} FROM {table}"

def curry_query(table):
    return lambda columns: query(table, columns)

users_query = curry_query("users")
print(users_query("name, email"))  # Output: SELECT name, email FROM users
```

***

**9. Currying for Custom Calculations**

Creating a curried function for complex calculations.

```python
def calculate(a, b, c):
    return (a + b) * c

def curry_calculate(a, b):
    return lambda c: calculate(a, b, c)

calc_5_3 = curry_calculate(5, 3)
print(calc_5_3(2))  # Output: 16
```

***

**10. Currying for String Formatting**

Currying a function to format strings.

```python
def format_string(template, *args):
    return template.format(*args)

def curry_format(template):
    return lambda *args: format_string(template, *args)

greet_user = curry_format("Hello, {}!")
print(greet_user("Alice"))  # Output: Hello, Alice!
```

***

These examples demonstrate how currying can simplify function calls by allowing you to pre-fill arguments and create more specialized versions of functions. The technique enhances code readability, especially in cases where certain values are fixed or repeated across multiple calls.
