Where can I use my variable?

Syntax and values

A variable has a name and a place where that name can be used. That place is its scope. Imagine a small box inside a larger box: code can look for names in its own box and then the surrounding boxes. The small box is a child scope.

Pliro uses lexical scope: the arrangement of your written code determines those boxes. Calling a function from somewhere else does not change its surrounding scope. Local names belong inside a block or function; global names belong at program level, outside those blocks.

A declaration creates a name. Pliro makes it available from that point onwards. Some languages make later declarations available earlier, called hoisting; Pliro does not. A function can call itself, called recursion. Mutual recursion means two functions call each other; using a later function declaration this way is not supported. Prelude names are the built-in names Pliro supplies before your own declarations.

Try a name inside a block

The two variables below share a spelling but belong to different scopes. The inner name is used inside the block; the outer name is used afterwards. This is called shadowing.

# language: en
let message = "outside"
if true:
    let message = "inside"
    say message
say message

Predict the order before running it. You should see inside, then outside. Changing a value with set is different from declaring a new inner variable with let.

Source-order declarations

Declarations become visible in source order. A later declaration cannot be
used earlier in the same scope. A function name is visible inside its own body,
which permits direct recursion, but later functions are not generally hoisted.

function countdown(value):
    if value > 0:
        say value
        countdown(value - 1)

Mutual recursion through a later declaration is not supported by source-order
name resolution.

Scope boundaries

Pliro uses lexical scope:

Construct Scope behavior
Program Own top-level scope.
if body New child scope.
else body Separate new child scope.
Each while iteration Fresh child scope.
Each for iteration Fresh child scope containing the loop variable.
Function New scope for parameters and direct body declarations.
Nested block May shadow an enclosing name.

Declaring the same name twice in one scope is an error. A child scope may shadow an outer name, as in the first example on this page.

Assignment across scopes

set searches the current and enclosing lexical scopes. It may update a
variable, parameter, or loop variable. It may not update a user function,
built-in function, or module namespace.

Built-in shadowing

Standard-library aliases are ordinary prelude names and may be shadowed:

let say = "not a function"

After that declaration, say("Hello") attempts to call the text value and
fails at runtime. Avoid reusing built-in names unless shadowing is deliberate.

Syntax and values · Built-ins · Programming guides