{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "function-design-title",
   "metadata": {},
   "source": [
    "# Function Design\n",
    "\n",
    "Good functions are small, named clearly, documented when needed, and composed with other functions. This section focuses on scope, composition, docstrings, and basic lambda expressions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9b6fb95",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Find project root by looking for _config.yml\n",
    "current = Path.cwd()\n",
    "for parent in [current, *current.parents]:\n",
    "    if (parent / '_config.yml').exists():\n",
    "        project_root = parent\n",
    "        break\n",
    "else:\n",
    "    project_root = Path.cwd().parent.parent\n",
    "\n",
    "# Add project root to path\n",
    "sys.path.insert(0, str(project_root))\n",
    "\n",
    "# Import shared teaching helpers and cell magics\n",
    "from shared import thinkpython, diagram, jupyturtle, structshape\n",
    "from shared.download import download\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aee17814-92f9-4744-8c8d-dd738767a67b",
   "metadata": {},
   "source": [
    "## Scope\n",
    "\n",
    "Variables created inside functions are **local** to that function:\n",
    "\n",
    "```python\n",
    "def my_function():\n",
    "    local_var = 10  # Only exists inside the function\n",
    "    print(local_var)\n",
    "\n",
    "my_function()     # Prints: 10\n",
    "# print(local_var)  # Error: local_var doesn't exist here\n",
    "```\n",
    "\n",
    "Variables outside functions are **global**:\n",
    "\n",
    "```python\n",
    "global_var = 100  # Accessible everywhere\n",
    "\n",
    "def show_global():\n",
    "    print(global_var)  # Can read global variable\n",
    "\n",
    "show_global()  # Prints: 100\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9923f7df-8dcd-4a44-a6fb-dd70b36259bb",
   "metadata": {},
   "source": [
    "### Variables and Parameters Are Local\n",
    "\n",
    "When you create a variable inside a function, it is **local**, which means it exists only inside the function. For example, the following function takes two arguments, con**cat**enates them, and prints the result twice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c397c812-43db-4bb6-b695-797c75159329",
   "metadata": {},
   "outputs": [],
   "source": [
    "def greet():\n",
    "    message = \"Hello\"\n",
    "    print(message)\n",
    "\n",
    "greet()\n",
    "\n",
    "try:\n",
    "    print(message)   ### cannot do this because message is local\n",
    "except NameError as error:\n",
    "    print(type(error).__name__)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "977798e0-3256-44f9-a903-08bd7ef1c8a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def greet():\n",
    "    global message        ### must declare first\n",
    "    message = \"Hello\"\n",
    "    print(message)\n",
    "\n",
    "greet()\n",
    "\n",
    "print(message)   ### can do! \"message\" variable is now global in the function definition.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b97534fe-43bb-41e1-80ab-c80fd15ba083",
   "metadata": {},
   "source": [
    "## Function Composition\n",
    "\n",
    "Function composition is the process of using the output of one function as the input to another."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 174,
   "id": "7a2932f9-a9a2-4733-a480-934fbab6f54e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "36\n"
     ]
    }
   ],
   "source": [
    "def double(x):\n",
    "    return x * 2\n",
    "\n",
    "def square(x):\n",
    "    return x ** 2\n",
    "\n",
    "result = square(double(3))\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 175,
   "id": "ex7-q-59a52e59",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### === EXERCISE 7: Function Composition ===\n",
    "### Write three functions:\n",
    "### - calculate_area(length, width): returns the area\n",
    "### - format_result(value): returns 'Area is X square units'\n",
    "### Then write report_rectangle() that uses both to calculate and format.\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code stops here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 176,
   "id": "ex7-a-4daaf03f",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Area is 15 square units\n",
      "Area is 20 square units\n"
     ]
    }
   ],
   "source": [
    "### Solution:\n",
    "def calculate_area(length, width):\n",
    "    return length * width\n",
    "\n",
    "def format_result(value):\n",
    "    return f'Area is {value} square units'\n",
    "\n",
    "### Function composition: one function calls another\n",
    "def report_rectangle(length, width):\n",
    "    area = calculate_area(length, width)\n",
    "    return format_result(area)\n",
    "\n",
    "### Test\n",
    "print(report_rectangle(5, 3))\n",
    "print(report_rectangle(10, 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 177,
   "id": "ex6-q-231171d8",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### === EXERCISE 9: Variable Scopes ===\n",
    "### Write a function called modify_variables() that:\n",
    "### - Takes a parameter x\n",
    "### - Creates a local variable local_var = x * 2\n",
    "### - Prints both x and local_var inside the function\n",
    "### Then call it and try to print local_var outside (it should fail).\n",
    "\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 178,
   "id": "ex6-a-6431a37c",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Inside: x=5, local_var=10\n"
     ]
    }
   ],
   "source": [
    "### Solution:\n",
    "def modify_variables(x):\n",
    "    local_var = x * 2\n",
    "    print(f\"Inside: x={x}, local_var={local_var}\")\n",
    "\n",
    "### Call the function\n",
    "modify_variables(5)\n",
    "\n",
    "### This will cause NameError because local_var doesn't exist outside\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "495cb571-e8a9-4865-ba61-9622564f54e6",
   "metadata": {},
   "source": [
    "When we run `print_verse`, it calls `first_two_lines`, which calls `repeat`, which calls `print`.\n",
    "That's a lot of functions.\n",
    "\n",
    "Of course, we could have done the same thing with fewer functions, but the point of this example is to show how functions can work together."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "105e0774-6927-4c40-866d-58264f19f216",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "## Docstrings\n",
    "\n",
    "**Docstrings** (**documentation** strings) are used to document **functions**/methods, _classes_, and _modules_. They use _triple quotes_ and should be the first statement after defining a function or class. \n",
    "\n",
    "Always document your functions with docstrings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 179,
   "id": "95498a60",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Function with docstring\n",
    "def greet(name):\n",
    "    \"\"\"\n",
    "    This function does xxx and yyy.         ### 1. what this function is about\n",
    "    \n",
    "    Args:                                   ### 2. input parameters\n",
    "        name: The person's name (string)    \n",
    "    \n",
    "    Returns:                                ### 3. what the function returns\n",
    "        A greeting message (string)\n",
    "    \"\"\"\n",
    "    return f\"Hello, {name}!\"\n",
    "\n",
    "message = greet(\"Homer\")       ### call the function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5dbe1c8-83ec-4ef7-bb72-f936e4806a93",
   "metadata": {},
   "source": [
    "As an example:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f1bb7d6-611f-4af5-979d-9f91fb0e2bea",
   "metadata": {},
   "source": [
    "```python\n",
    "def calculate_bmi(weight, height):\n",
    "    \"\"\"\n",
    "    Calculate Body Mass Index (BMI).\n",
    "    \n",
    "    Args:\n",
    "        weight: Weight in kilograms (float)\n",
    "        height: Height in meters (float)\n",
    "    \n",
    "    Returns:\n",
    "        BMI value (float)\n",
    "    \n",
    "    Example:\n",
    "        >>> calculate_bmi(70, 1.75)\n",
    "        22.86\n",
    "    \"\"\"\n",
    "    return weight / (height ** 2)\n",
    "```\n",
    "\n",
    "Good docstrings include:\n",
    "1. What the function does\n",
    "2. Parameters and their types\n",
    "3. What the function returns\n",
    "4. Usage examples (optional)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 180,
   "id": "4bef9858-6692-4dc9-b2bf-534c14390028",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Testing calculate_discount:\n",
      "  $100 with 20% off = $80.0\n",
      "\n",
      "Testing is_valid_email:\n",
      "  'user@example.com' is valid: True\n",
      "  'invalid-email' is valid: False\n",
      "\n",
      "Testing celsius_to_fahrenheit:\n",
      "  0°C = 32.0°F\n",
      "  100°C = 212.0°F\n",
      "  37°C = 98.6°F\n"
     ]
    }
   ],
   "source": [
    "### Practical functions with docstrings\n",
    "\n",
    "def calculate_discount(price, discount_percent):\n",
    "    \"\"\"\n",
    "    Calculate the final price after applying a discount.\n",
    "    \n",
    "    Args:\n",
    "        price: Original price (float or int)\n",
    "        discount_percent: Discount percentage (0-100)\n",
    "    \n",
    "    Returns:\n",
    "        Final price after discount (float)\n",
    "    \"\"\"\n",
    "    discount_amount = price * (discount_percent / 100)\n",
    "    final_price = price - discount_amount\n",
    "    return final_price\n",
    "\n",
    "def is_valid_email(email):\n",
    "    \"\"\"\n",
    "    Check if email has basic valid format.\n",
    "    \n",
    "    Args:\n",
    "        email: Email address to validate (str)\n",
    "    \n",
    "    Returns:\n",
    "        True if email contains @ and ., False otherwise\n",
    "    \"\"\"\n",
    "    return '@' in email and '.' in email\n",
    "\n",
    "def celsius_to_fahrenheit(celsius):\n",
    "    \"\"\"\n",
    "    Convert Celsius to Fahrenheit.\n",
    "    \n",
    "    Args:\n",
    "        celsius: Temperature in Celsius\n",
    "    \n",
    "    Returns:\n",
    "        Temperature in Fahrenheit\n",
    "    \"\"\"\n",
    "    return (celsius * 9/5) + 32\n",
    "\n",
    "### Test the functions\n",
    "print(\"Testing calculate_discount:\")\n",
    "original = 100\n",
    "discount = 20\n",
    "final = calculate_discount(original, discount)\n",
    "print(f\"  ${original} with {discount}% off = ${final}\")\n",
    "\n",
    "print(\"\\nTesting is_valid_email:\")\n",
    "print(f\"  'user@example.com' is valid: {is_valid_email('user@example.com')}\")\n",
    "print(f\"  'invalid-email' is valid: {is_valid_email('invalid-email')}\")\n",
    "\n",
    "print(\"\\nTesting celsius_to_fahrenheit:\")\n",
    "print(f\"  0°C = {celsius_to_fahrenheit(0)}°F\")\n",
    "print(f\"  100°C = {celsius_to_fahrenheit(100)}°F\")\n",
    "print(f\"  37°C = {celsius_to_fahrenheit(37):.1f}°F\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 181,
   "id": "ex8-q-8a5c47d0",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### === EXERCISE 10: Writing Good Docstrings ===\n",
    "### Write a function called validate_password() that:\n",
    "### - Takes a password string as a parameter\n",
    "### - Checks if it's at least 8 characters long\n",
    "### - Returns True if valid, False otherwise\n",
    "### Include a comprehensive docstring with description, Args, and Returns sections.\n",
    "\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 182,
   "id": "ex8-a-e14ff35b",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "### Solution:\n",
    "def validate_password(password):\n",
    "    \"\"\"\n",
    "    Validate if a password meets minimum length requirements.\n",
    "    \n",
    "    Args:\n",
    "        password (str): The password to validate\n",
    "    \n",
    "    Returns:\n",
    "        bool: True if password is at least 8 characters, False otherwise\n",
    "    \"\"\"\n",
    "    return len(password) >= 8\n",
    "\n",
    "### Test the function\n",
    "print(validate_password(\"short\"))        ### False\n",
    "print(validate_password(\"verylongpassword\"))  ### True\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c46d682b",
   "metadata": {},
   "source": [
    "## Lambda Functions\n",
    "\n",
    "**Syntax:** `lambda arguments: expression`\n",
    "\n",
    "Lambda functions are small, anonymous functions that can have any number of arguments but can only have one expression. They are useful for short, simple functions that you don't want to define formally."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ef797aa",
   "metadata": {},
   "source": [
    "### Lambda Limitations\n",
    "\n",
    "- Can only contain expressions, not statements\n",
    "- Cannot contain assignments, print statements, or other statements\n",
    "- Best for simple, one-line functions\n",
    "- For complex logic, use regular functions (`def`)\n",
    "\n",
    "If you find yourself writing a multi-line `lambda`, switch to a normal `def` function instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 183,
   "id": "31ac93fd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25\n",
      "7\n"
     ]
    }
   ],
   "source": [
    "### Basic lambda function\n",
    "square = lambda x: x ** 2\n",
    "print(square(5))  # Output: 25\n",
    "\n",
    "### Lambda with multiple arguments\n",
    "add = lambda x, y: x + y\n",
    "print(add(3, 4))  # Output: 7"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": ".venv (3.13.7)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
