Scoping: Geometric Scene Evolution as Programming Pedagogy

This document articulates ggblab’s foundational educational mission: use geometric scene construction to teach variable scoping and computational thinking—concepts often neglected or poorly understood in Python education.


Part 1: Pedagogical Foundation

The Problem: Python’s Scoping Crisis in Education

Why Python Fails at Teaching Scoping

Python’s dynamic nature and permissive scoping rules make it a poor vehicle for teaching fundamental programming concepts:

Issue

Python Behavior

Educational Consequence

Implicit global access

Variables can be read from outer scopes without declaration

Students don’t understand scope boundaries

Late binding

Closures capture variable references, not values

Confusing behavior in loops (lambda i: i problem)

Weak encapsulation

No private variables; convention-based (_var)

Students don’t internalize information hiding

Namespace pollution

from module import * is legal

No sense of explicit dependencies

Mutable default arguments

def f(x=[]): creates shared state

Violates expectations of function isolation

Result: Students write code that “works” but have no mental model of:

  • Where variables are defined (scope boundaries)

  • What depends on what (dependency relationships)

  • When values are captured vs. referenced (binding semantics)

  • How to reason about side effects

Traditional Approaches Fall Short

Textbook explanations: Abstract diagrams of “local,” “enclosing,” “global,” “built-in” scopes (LEGB rule)

  • ❌ No concrete, visual anchor

  • ❌ Disconnected from real problem-solving

  • ❌ Students memorize without understanding

Toy examples: x = 1 at global level, x = 2 inside function

  • ❌ Artificial; no meaningful context

  • ❌ Doesn’t generalize to complex programs

  • ❌ Boring; students disengage

What’s missing: A domain that students already care about (mathematics) where scoping naturally emerges.


The Solution: Geometric Scene Construction as Scoping Model

Why Geometry Works

Mathematical constructions are inherently scoped:

Given:
  - Points A, B        [Base scope: axioms/givens]
  
Construct:
  - Line AB            [Scope 1: depends on A, B]
  - Midpoint M of AB   [Scope 2: depends on AB, hence on A, B]
  - Perpendicular bisector L of AB  [Scope 2: depends on AB]
  - Point C on L       [Scope 3: depends on L, hence on AB, A, B]
  - Triangle ABC       [Scope 4: depends on A, B, C]
  
Property to prove:
  - Triangle ABC is isosceles  [Verification scope: accesses A, B, C, M, L] 

This is a scoping hierarchy:

  • Base scope (Points A, B): Foundational definitions; no dependencies

  • Scope 1 (Line AB): Depends on base scope; closes over A, B

  • Scope 2 (Midpoint M, Bisector L): Depends on Scope 1; transitively depends on base

  • Scope 3 (Point C): Depends on Scope 2; long dependency chain

  • Verification scope: Reads from multiple scopes; proves emergent properties

The Isomorphism: Geometry ↔ Programming

Geometric Concept

Programming Concept

ggblab Manifestation

Base points (A, B)

Function parameters / global constants

User-defined initial state

Constructed line (AB)

Local variable depending on parameters

Line(A, B) object in dependency graph

Derived point (M = midpoint)

Computed value from local variables

Midpoint(A, B) in SymPy/GeoGebra

Constraint (C on L)

Variable binding within scope

Point’s position determined by ancestor objects

Property verification

Function accessing multiple scopes

SymPy verify_property() traverses graph

Construction sequence

Call stack / execution order

Topological sort of dependency graph

Parameter sweep (vary A)

Closure capturing mutable reference

Scene Timeline records scope evolution

Visual Manifestation in GeoGebra

GeoGebra’s construction protocol IS a scope tree:

Construction Protocol:
1. A = (1, 2)           → Scope: Global (user-defined)
2. B = (4, 6)           → Scope: Global (user-defined)
3. AB = Line[A, B]      → Scope: Local to AB; depends on {A, B}
4. M = Midpoint[A, B]   → Scope: Local to M; depends on {A, B}
5. L = PerpendicularLine[M, AB] → Scope: Local to L; depends on {M, AB} → {A, B}
6. C = Point[L]         → Scope: Local to C; depends on {L} → {M, AB} → {A, B}

Dependency graph (generated by ggblab’s parse() method):

    A ──┐
        ├──> AB ──> L ──> C
    B ──┘      └──> M ──┘

This IS a scope tree:

  • Root nodes (A, B): No dependencies → global scope

  • Internal nodes (AB, M, L): Depend on ancestors → nested scopes

  • Leaf nodes (C): Depend on long chains → deeply nested scopes

Students see the scoping structure visually. No abstract diagrams needed.


Computational Thinking via Geometric Construction

The Four Pillars

Computational Thinking (Wing, 2006) comprises:

  1. Decomposition: Break complex problems into manageable parts

  2. Pattern Recognition: Identify similarities and recurring structures

  3. Abstraction: Focus on essential information; hide irrelevant details

  4. Algorithm Design: Create step-by-step solutions

Geometric construction naturally teaches all four:

Decomposition

Geometry: “Construct an equilateral triangle”

  • Step 1: Define base segment AB

  • Step 2: Construct circle centered at A with radius |AB|

  • Step 3: Construct circle centered at B with radius |AB|

  • Step 4: Find intersection C of the two circles

  • Step 5: Triangle ABC is equilateral

Programming: “Write a function to compute factorial”

  • Step 1: Base case (n = 0)

  • Step 2: Recursive case (n * factorial(n-1))

  • Step 3: Combine

Skill transfer: Both require breaking monolithic tasks into ordered subtasks.

Pattern Recognition

Geometry: Recognize that “perpendicular bisector + midpoint” pattern appears in many constructions

  • Isosceles triangle construction

  • Square construction

  • Circle-tangent constructions

Programming: Recognize that “initialize accumulator + iterate + update” pattern appears in many algorithms

  • Summing a list

  • Finding maximum

  • Filtering data

Skill transfer: Both require identifying reusable templates.

Abstraction

Geometry:

  • Abstract away specific coordinates: “Triangle ABC” (not “Triangle at (0,0), (1,0), (0.5, √3/2)”)

  • Focus on relationships: “Perpendicular” (not “slope = -1/m”)

Programming:

  • Abstract away implementation: sort(list) (not manual quicksort)

  • Focus on interfaces: “This function takes a list and returns a sorted list”

Skill transfer: Both require ignoring low-level details to reason at higher levels.

Algorithm Design

Geometry: “Given three non-collinear points, construct the circumcircle”

  • Algorithm 1: Perpendicular bisectors of two sides intersect at circumcenter

  • Algorithm 2: Use formula for circumcenter coordinates

  • Trade-offs: Geometric vs. algebraic; visual clarity vs. computational efficiency

Programming: “Sort a list of numbers”

  • Algorithm 1: Bubble sort (simple, slow)

  • Algorithm 2: Merge sort (complex, fast)

  • Trade-offs: Simplicity vs. efficiency

Skill transfer: Both require designing step-by-step procedures and evaluating trade-offs.


Scoping Pedagogy: From Math to Code

Concrete Example: Isosceles Triangle Construction

Mathematical Statement:

Construct an isosceles triangle ABC where AB is the base.

Geometric Construction (GeoGebra):

Step 1: Define base segment
  A = Point(0, 0)           # Scope: Global (parameter)
  B = Point(4, 0)           # Scope: Global (parameter)

Step 2: Construct perpendicular bisector
  M = Midpoint(A, B)        # Scope: Nested; depends on {A, B}
  AB = Segment(A, B)        # Scope: Nested; depends on {A, B}
  L = PerpendicularLine(M, AB)  # Scope: Nested; depends on {M, AB}

Step 3: Choose point on bisector
  C = Point(L)              # Scope: Nested; depends on {L}
                            # Transitively depends on {M, AB, A, B}

Result: Triangle(A, B, C) is isosceles (AC = BC)

Dependency Graph (ggblab parse() output):

A ──┬──> M ──┐
    │         ├──> L ──> C
    └──> AB ──┘
B ──┘

Translation to Python Scoping:

# Global scope: Parameters (like A, B in geometry)
def construct_isosceles_triangle(base_length=4):
    # Scope 1: Define base points (like A, B)
    A = Point(0, 0)
    B = Point(base_length, 0)
    
    # Scope 2: Derived objects (like M, AB, L)
    def perpendicular_bisector():
        # This function "closes over" A and B
        # (Like M and L depend on A and B)
        midpoint_x = (A.x + B.x) / 2
        midpoint_y = (A.y + B.y) / 2
        M = Point(midpoint_x, midpoint_y)
        
        # Perpendicular direction
        dx = B.x - A.x
        dy = B.y - A.y
        perp_dx, perp_dy = -dy, dx  # Rotate 90°
        
        return M, (perp_dx, perp_dy)
    
    # Scope 3: Choose point on bisector (like C on L)
    M, perp_dir = perpendicular_bisector()
    height = 3  # User-chosen parameter
    C = Point(M.x + height * perp_dir[0], 
              M.y + height * perp_dir[1])
    
    # Return: Triangle closure
    # (Like Triangle ABC in geometry)
    return Triangle(A, B, C)

# Verification: Access to outer scopes
triangle = construct_isosceles_triangle(base_length=4)
assert triangle.is_isosceles()  # Reads A, B, C from nested scopes

What students learn:

  1. Scope boundaries: Variables defined in perpendicular_bisector() are not accessible outside

  2. Closure semantics: Inner function captures A and B from outer scope

  3. Dependency chains: C depends on M, which depends on A and B

  4. Verification scope: is_isosceles() can read from multiple construction scopes

Key insight: The geometric construction makes scoping tangible and visual, not abstract.

Parameter Sweeps and Scope Evolution

Geometric Scenario: Vary the height of point C along the perpendicular bisector.

Scene Timeline (ggblab v0.8):

# Define parametric construction
def construct_with_height(h):
    A = Point(0, 0)
    B = Point(4, 0)
    M = Midpoint(A, B)
    C = Point(M.x, M.y + h)  # Height parameter
    return {'A': A, 'B': B, 'C': C, 'triangle': Triangle(A, B, C)}

# Sweep height parameter
timeline = SceneTimeline()
for h in np.linspace(0, 5, 20):
    scene = construct_with_height(h)
    timeline.capture(scene, metadata={'height': h})

# Playback: Watch C move along bisector
timeline.play()

Scoping lesson:

  • Parameter binding: h is a parameter; each scene snapshot captures a different binding

  • Closure behavior: At each h, the construction “closes over” that specific value

  • Scope lifetime: Old snapshots preserve their scope (like call stack frames)

Programming analogy: Factory functions and closures

def make_multiplier(n):
    # n is captured in closure
    def multiply(x):
        return x * n  # Accesses n from outer scope
    return multiply

times_2 = make_multiplier(2)  # Captures n=2
times_3 = make_multiplier(3)  # Captures n=3

# Each closure has its own scope
print(times_2(5))  # 10 (uses n=2)
print(times_3(5))  # 15 (uses n=3)

Geometric equivalent: Each scene snapshot is a closure that captured specific parameter values.

Students see: Scope is not just static structure; it evolves with program execution.


Part 2: Implementation

Dependency Graph as Scope Tree

ggblab’s parse() method (in ggblab/parser.py) constructs a directed graph:

def parse(self, protocol: str) -> nx.DiGraph:
    """
    Parse GeoGebra construction protocol into dependency graph.
    
    Returns:
        NetworkX DiGraph where:
        - Nodes: GeoGebra objects (points, lines, circles, etc.)
        - Edges: (A -> B) means "B depends on A"
    """
    # ... parse XML ...
    
    # Build graph
    G = nx.DiGraph()
    for obj in construction_objects:
        G.add_node(obj['name'], **obj['properties'])
        for dependency in obj['depends_on']:
            G.add_edge(dependency, obj['name'])
    
    return G

This graph IS the scope tree:

  • Root nodes (in_degree == 0): Global scope / parameters

  • Leaf nodes (out_degree == 0): Terminal constructions (no dependents)

  • Paths: Scope nesting depth (path length from root to node)

Educational workflow:

# Student constructs in GeoGebra
# Export to ggblab

from ggblab import GeoGebraApp
ggb = GeoGebraApp()

# Parse dependency graph
graph = ggb.parse(ggb.get_construction_protocol())

# Visualize scope tree
import matplotlib.pyplot as plt
import networkx as nx

pos = nx.spring_layout(graph)
nx.draw(graph, pos, with_labels=True, node_color='lightblue', 
        node_size=500, font_size=10, arrows=True)
plt.title("Dependency Graph = Scope Tree")
plt.show()

# Identify scope levels (topological sort)
scope_levels = list(nx.topological_generations(graph))
for i, level in enumerate(scope_levels):
    print(f"Scope Level {i}: {level}")

# Output:
# Scope Level 0: ['A', 'B']           # Global scope
# Scope Level 1: ['AB', 'M']          # Depends on Level 0
# Scope Level 2: ['L']                # Depends on Level 1
# Scope Level 3: ['C']                # Depends on Level 2

Students see: The graph structure reveals scoping hierarchy.

SymPy Integration and Symbolic Scoping

SymPy objects have explicit dependencies (unlike Python variables):

from sympy import symbols
from sympy.geometry import Point, Line, Circle

# Symbolic parameters (global scope)
a, b = symbols('a b')

# Scope 1: Parameterized points
A = Point(0, 0)
B = Point(a, b)

# Scope 2: Derived objects
AB = Line(A, B)
M = Point(a/2, b/2)  # Midpoint formula

# Scope 3: Constrained objects
# C is on perpendicular through M
# Symbolic expression for C's coordinates
from sympy import sqrt, solve, Eq

# C is distance d from M along perpendicular
d = symbols('d', positive=True)
slope_AB = b / a  # Slope of AB
slope_perp = -a / b  # Perpendicular slope

C_x = M.x + d / sqrt(1 + slope_perp**2)
C_y = M.y + d * slope_perp / sqrt(1 + slope_perp**2)
C = Point(C_x, C_y)

print(f"C depends on: {C.free_symbols}")
# Output: {a, b, d}  ← Explicit scope dependencies!

Key teaching moment:

  • SymPy tracks scope automatically via free_symbols

  • Students see dependencies explicitly: “C depends on a, b, d”

  • No guessing about closures: Symbolic computation makes scoping transparent

ggblab integration:

# Convert GeoGebra construction to SymPy
from ggblab.sympy_bridge import geogrebra_to_sympy

sympy_objects = geogrebra_to_sympy(ggb.get_base64())

# Inspect scope dependencies
for name, obj in sympy_objects['objects'].items():
    if hasattr(obj, 'free_symbols'):
        deps = obj.free_symbols
        print(f"{name} depends on: {deps}")

# Output:
# A depends on: set()           # No dependencies (global scope)
# B depends on: set()           # No dependencies (global scope)
# AB depends on: set()          # Depends on A, B (already concrete)
# M depends on: set()           # Depends on A, B (already concrete)
# C depends on: {t}             # Depends on parameter t

Students learn: Scoping isn’t magic; it’s explicit dependency tracking.


Classroom Integration

Lesson Progression

Lesson 1: Introduction to Scope via Points and Lines

  • Goal: Understand that some objects depend on others

  • Activity:

    1. Construct two points A, B in GeoGebra

    2. Construct line AB

    3. Try to delete A → GeoGebra warns “Line AB depends on A”

    4. Export to ggblab; visualize dependency graph

  • Learning Outcome: “Dependencies are like scope; child objects need parent objects”

Lesson 2: Nested Scopes via Construction Chains

  • Goal: Understand transitive dependencies

  • Activity:

    1. Construct: A → B → AB → M (midpoint) → L (perpendicular) → C (point on L)

    2. Export to ggblab; compute scope levels (topological sort)

    3. Compare to Python nested functions

  • Learning Outcome: “Scope depth = dependency chain length”

Lesson 3: Closures via Parameter Sweeps

  • Goal: Understand closure semantics and parameter binding

  • Activity:

    1. Define slider h in GeoGebra (height parameter)

    2. Construct triangle with vertex at height h

    3. Sweep h using ggblab Scene Timeline

    4. Compare to Python closure capturing variable

  • Learning Outcome: “Each snapshot captures specific parameter values (like closures)”

Lesson 4: Verification Scope via Symbolic Proofs

  • Goal: Understand that verification can access multiple scopes

  • Activity:

    1. Construct isosceles triangle (as above)

    2. Use ggblab + SymPy to verify AC == BC symbolically

    3. Note that verification reads from multiple construction scopes

    4. Compare to Python function accessing outer scopes

  • Learning Outcome: “Verification scope can read (but not modify) construction scopes”

Assessment Rubric

Objective: Can students transfer geometric scoping to programming scoping?

Criterion

Novice

Intermediate

Advanced

Identify dependencies

Cannot identify which objects depend on which

Can identify direct dependencies (A → B)

Can trace transitive dependencies (A → B → C)

Scope boundaries

Doesn’t understand why deleting A breaks AB

Understands parent-child relationships

Can predict cascading effects of modifications

Closure behavior

Cannot explain parameter binding

Understands that snapshots capture parameters

Can implement closures in Python using geometric intuition

Transfer to code

Cannot relate geometry to programming

Can see similarities but not apply

Can write scoped Python code using geometric templates

Example Exercise

Geometric Problem:

Given points A, B, C, construct the incenter I of triangle ABC.

Construction Steps:

  1. Define A, B, C (global scope)

  2. Construct angle bisectors (nested scope; depend on A, B, C)

  3. Find intersection I of bisectors (nested scope; depends on bisectors)

Programming Challenge:

Translate the construction to Python code with proper scoping.

Expected Solution:

def construct_incenter(A, B, C):
    """
    Scope hierarchy:
    - Parameters: A, B, C (like geometric givens)
    - Nested: angle bisectors (depend on A, B, C)
    - Nested: incenter I (depends on bisectors)
    """
    # Scope 1: Angle bisectors
    def angle_bisector(vertex, side1, side2):
        # Closure over vertex, side1, side2
        # ... compute bisector direction ...
        return bisector_line
    
    # Scope 2: Compute bisectors
    bisector_A = angle_bisector(A, B, C)
    bisector_B = angle_bisector(B, A, C)
    
    # Scope 3: Intersection = incenter
    I = intersect(bisector_A, bisector_B)
    
    return I

# Verification scope: Check that I is equidistant from all sides
incenter = construct_incenter(A, B, C)
assert distance(incenter, side_AB) == distance(incenter, side_BC)

Grading Criteria:

  • ✅ Correct scope nesting (inner functions don’t leak)

  • ✅ Closure semantics (bisectors capture vertex coordinates)

  • ✅ Dependency clarity (I depends on bisectors, which depend on A, B, C)


Why This Matters: The Meta-Goal

Computational Thinking Is Transferable

The educational research consensus (Barr & Stephenson, 2011; Brennan & Resnick, 2012):

  • Computational thinking skills transfer across domains

  • Abstract concepts need concrete anchors

  • Mathematics is the ideal anchor for programming concepts

ggblab bridges the gap:

Mathematics (already understood)
    ↓ Geometric construction
Dependency relationships (visual, tangible)
    ↓ ggblab translation
Programming scoping (abstract, conceptual)

Traditional CS education fails because it starts with abstract scoping rules. ggblab succeeds because it starts with concrete geometric relationships.

The Pipeline: From Geometry to Professional Programming

Stage 1: Geometric Exploration (K-8)

  • Students learn geometric constructions in GeoGebra

  • No programming; pure mathematics

Stage 2: Dependency Awareness (Grades 9-10)

  • Students export constructions to ggblab

  • Visualize dependency graphs

  • Understand “this depends on that”

Stage 3: Scoping Translation (Grades 11-12 / College)

  • Students write Python code mirroring geometric constructions

  • Explicit teaching: “Geometric dependency = programming scope”

  • Exercises: Translate constructions to code

Stage 4: Professional Programming (College / Career)

  • Students write complex software with proper scoping

  • Intuition: “Check the dependency graph; avoid circular references”

  • Debugging: “Trace the scope chain; where was this variable defined?”

ggblab is the missing link between Stages 2 and 3.

Addressing Python’s Weaknesses

Python’s educational value is undeniable (simple syntax, rich libraries). But its scoping weaknesses are real.

ggblab compensates by:

  1. Externalizing scope: Make dependencies visual (graph), not implicit

  2. Symbolic tracking: SymPy’s free_symbols makes scope explicit

  3. Geometric grounding: Anchor abstract scoping in tangible construction

Result: Students learn scoping principles (not just Python’s LEGB rule) that transfer to any language (JavaScript, Java, Rust, etc.).


Implementation Roadmap

v0.8: Basic Scoping Visualization

  • [x] Dependency graph construction (parse() method)

  • [ ] Graph visualization with scope-level highlighting

  • [ ] Example notebook: “Geometric Dependencies as Scope Trees”

v0.9: Symbolic Scoping

  • [ ] SymPy integration (geogrebra_to_sympy())

  • [ ] Automatic scope analysis (track free_symbols)

  • [ ] Example notebook: “Symbolic Dependencies and Closure Semantics”

v1.0: Parameter Sweeps and Scope Evolution

  • [ ] Scene Timeline with parameter binding visualization

  • [ ] Side-by-side: geometric scene + scope tree + Python closure

  • [ ] Example notebook: “Closures via Geometric Parameter Sweeps”

v1.1: Educational Lesson Modules

  • [ ] Lesson 1: Points and Lines (Introduction to Dependencies)

  • [ ] Lesson 2: Construction Chains (Transitive Dependencies)

  • [ ] Lesson 3: Parameter Sweeps (Closures)

  • [ ] Lesson 4: Verification (Multi-Scope Access)

v1.2: Assessment and Feedback

  • [ ] Automated exercises: “Translate this construction to code”

  • [ ] Rubric-based grading (dependency identification, scope boundaries, etc.)

  • [ ] Instructor dashboard: Track student understanding of scoping


Success Metrics

Short-Term (v1.0)

  • [ ] Students can identify dependencies in geometric constructions (100% accuracy on simple graphs)

  • [ ] Students can map geometric dependencies to Python scopes (75% accuracy on translation exercises)

  • [ ] Students report: “I finally understand closures” (qualitative feedback)

Medium-Term (v1.2)

  • [ ] Adoption by 1+ institution (geometry + CS course pairing)

  • [ ] Instructor feedback: “Students debug scope errors faster after using ggblab”

  • [ ] Comparative study: ggblab users vs. control group on scoping quiz (statistically significant improvement)

Long-Term (v2.0+)

  • [ ] Scoping pedagogy documented in CS education literature

  • [ ] ggblab referenced in computational thinking curricula

  • [ ] Transfer studies: Do ggblab users write better-scoped code in other languages? (JavaScript, Java, etc.)



Part 3: Philosophical Perspective

Philosophical Stance: Mathematics First

Why Not Teach Scoping Directly in Python?

Standard approach: Start with def, global, nonlocal, LEGB rule

  • ❌ Students memorize rules without mental models

  • ❌ No intuition for why scoping matters

  • ❌ Debugging scope errors is trial-and-error

ggblab approach: Start with geometric dependencies (already intuitive)

  • ✅ Students already understand “line depends on points”

  • ✅ Natural mental model: construction order = scope hierarchy

  • ✅ Debugging geometric constructions trains dependency reasoning

Principle: Teach concepts first in familiar domain (math), then transfer to code.

The Cognitive Science Rationale

Dual Coding Theory (Paivio, 1986):

  • Combining verbal (code) and visual (geometry) representations enhances learning

  • Students who see scoping both as code and as graphs outperform single-representation learners

Transfer of Learning (Perkins & Salomon, 1992):

  • Far transfer (math → programming) requires explicit bridging

  • ggblab provides the bridge: geometric dependency → programming scope

Constructivism (Piaget):

  • Students construct knowledge by connecting new concepts to existing schema

  • Geometric dependencies are existing schema (learned in math class)

  • Programming scopes are new concepts (connected via ggblab)


Summary

ggblab’s core educational mission is to use geometric scene construction as a visual, intuitive model for variable scoping—a concept poorly taught in Python and essential for computational thinking.

The insight:

  • Geometric dependencies (points → lines → circles → …) are isomorphic to programming scopes (global → function → nested → …)

  • Students already understand geometric dependencies from math class

  • ggblab bridges the gap: Export GeoGebra → visualize dependency graph → translate to Python scopes

The pipeline:

$$\text{Geometry (intuitive)} \xrightarrow{\text{ggblab}} \text{Dependency Graph (visual)} \xrightarrow{\text{mapping}} \text{Programming Scopes (abstract)}$$

Expected outcome: Students who learn scoping via ggblab will:

  1. Understand scope boundaries (not just memorize LEGB)

  2. Reason about dependencies (debug faster)

  3. Transfer to other languages (scoping principles are universal)

  4. Connect math and CS (computational thinking in both domains)

This is not just a tool; it’s a pedagogy grounded in cognitive science and mathematical reasoning.