Skip to content

Scope

No scope-rebinding declarations

A statement's meaning should not depend on some other statement elsewhere in the same function. Python's global/nonlocal violate this: whether x = value binds a new local or rebinds an enclosing name depends on a declaration that can sit anywhere in the function body, so an assignment can retroactively change what an earlier, unrelated-looking read meant:

counter = 0

def increment():
    print(counter)    # UnboundLocalError
    counter += 1      # this line is why
Lucid removes the declaration rather than live with that coupling: assignment is always local, independent of anything else in the function. Sharing mutable state across scopes instead goes through an explicit object, so the sharing is visible at the object's declaration and at each call site that touches it, rather than hidden behind a keyword elsewhere in the function.

No global

Python uses global to let assignment inside a function rebind a module binding:

counter = 0

def next_id() -> int:
    global counter
    counter += 1
    return counter
Lucid represents shared module state with an explicit mutable object:

counter = Cell(0)

def next_id() -> int:
    counter.value += 1
    return counter.value

No nonlocal

Python uses nonlocal to let assignment inside an inner function rebind a name from an enclosing function:

def make_counter():
    count = 0

    def next():
        nonlocal count
        count += 1
        return count

    return next
Closure state uses the same pattern:

def make_counter() -> () -> int:
    count = Cell(0)

    def next() -> int:
        count.value += 1
        return count.value

    return next