> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lionlang.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Lion environments: binding names to values

> Lion environments are plain JavaScript objects mapping string keys to values, forming a lexically-scoped chain consulted when a string expression is evaluated.

A Lion environment is a plain JavaScript object whose keys are strings and whose values can be anything the host runtime can express. When the evaluator encounters a string expression it consults the environment: if a matching key exists the bound value is returned, otherwise the string returns as-is. This single mechanism covers variable lookup, standard-library dispatch, and host-object interop simultaneously — no separate namespacing or import system is required.

## Structure

Internally, environments are held in Effect `Ref` cells and chained into a lexical scope tree:

* A **toplevel environment** has a `bindingsRef` but no parent. It is created by `run` from the caller-supplied bindings object.
* An **inner environment** has both a `bindingsRef` and a `parent` pointer. Lambda calls create an inner environment that extends the enclosing scope with the parameter bindings.

`getBinding` walks the chain from innermost outward, returning `Option.some(value)` at the first match or `Option.none()` when no binding exists at any level.

## String resolution

When the evaluator sees a string it calls `evaluateReference`:

```typescript theme={null}
export const evaluateReference = (name: string) =>
  pipe(
    getService(LionEnvironmentService),
    Effect.flatMap((environment) => getBinding(environment, name)),
    Effect.map(Option.getOrElse(() => name))
  );
```

If `getBinding` returns `Option.none()`, `Option.getOrElse(() => name)` substitutes the original string. This means an unbound string is **not an error** — it evaluates to itself. That makes bare string literals safe inside object values or `quote` expressions without any extra quoting.

## The stdlib default environment

`stdlib` is a pre-built environment that ships with `@lionlang/core`. Every entry is namespaced as `<module>/<name>`:

```json theme={null}
["number/add", 1, 2]
["string/concat", "lion", "ized"]
["array/map", ["array/make", 1, 2, 3], ["lambda", ["x"], ["number/add", "x", 1]]]
```

Passing `stdlib` directly to `run` gives access to all built-in modules without any additional setup.

## Extending the environment

Spread `stdlib` and add your own keys to inject custom values, host objects, or plain functions:

```typescript theme={null}
import { Effect } from "effect";
import { run } from "@lionlang/core/evaluation/evaluate";
import { stdlib } from "@lionlang/core/modules";

const env = {
  ...stdlib,
  price: 100,
  taxRate: 0.08,
  clamp: (min: number, max: number, value: number) =>
    Math.min(max, Math.max(min, value)),
};

await Effect.runPromise(
  run(["number/add", "price", ["number/multiply", "price", "taxRate"]], env)
);
// => 108

await Effect.runPromise(run(["clamp", 0, 100, 150], env));
// => 100
```

When `"price"` is evaluated as a string it resolves to `100`. When `"clamp"` is evaluated it resolves to the JavaScript function, which is then called by the function-call handler with the evaluated arguments.

<Note>
  Any JavaScript value — plain objects, class instances, native functions, or even other Effect computations — can live in the environment. Lion makes no assumptions about the shape of bound values beyond the fact that callable values are invoked as Lion functions when they appear as the head of an array expression.
</Note>

## Global mutation with `define`

The `define` special form writes a new binding into the **toplevel** environment, regardless of how deeply nested the current call is:

```json theme={null}
["define", "answer", 42]
```

After this evaluates, any subsequent expression in the same `run` call (or in a `begin` block) can reference `"answer"` and receive `42`. Lambdas created after the `define` also observe the new binding because they look up names at call time through the environment chain.

<Warning>
  `define` mutates the toplevel environment via a `Ref.update`. It is designed for sequential programs (e.g. inside `begin`) where ordering is explicit. Concurrent use of `define` in parallel effects is safe at the `Ref` level but may produce ordering that is hard to reason about.
</Warning>

## Lexical scope in lambdas

When `lambda` is evaluated it captures the **current** `LionEnvironmentService` value as `enclosedEnvironment`. Each call then creates a new inner environment extending that captured scope:

```typescript theme={null}
const enclosedEnvironment = yield* getService(LionEnvironmentService);
// ...
const environment = yield* makeEnvironment(paramsEnvironment, enclosedEnvironment);
```

This gives Lion lambdas true lexical scope: they close over the bindings visible at definition time while still observing global `define` mutations made after creation (because the global `Ref` is shared through the chain).
