This document describes how we want to implement an ephemeral “preview” mode in the Lava REPL, where a user’s typed expression is evaluated in a temporary clone of the interpreter state, to show the result but without producing any real side effects.
Additionally, we plan to adopt a default deny approach: no bridging function is allowed in ephemeral mode unless it is explicitly declared “preview safe.” As we progress, we will refine or add “stub” logic for certain side-effectful calls if needed.
math:plus, string:upper) can be whitelisted so ephemeral calls
proceed normally in the cloned environment. They either do not cause side effects at all, or we
have carefully verified that ephemeral calls are harmless.
file:write or math:srand),
ephemeral calls by default are forbidden.
Over time, if we want ephemeral usage for certain calls, we can define a “stub” method.
For example, math:srand might do nothing in ephemeral mode, returning a dummy result.
The plugin itself provides that stub if needed.
math Plugin as Pilot:
Because math is mostly pure, we can easily mark math:plus, math:pow,
math:sum, etc. as ephemeral-safe. Meanwhile, we forbid ephemeral usage of math:srand.
We then test ephemeral previews in real code, verifying that:
math:plus in the cloned interpreter produce correct results but do not alter the real environment.math:srand in ephemeral mode are blocked or stubbed, so we do not truly re-seed in the preview context.array:map is “pure” from the bridging perspective but the user’s function might do printing or file I/O.
The ephemeral logic can skip or block side-effectful callbacks unless we stub them too.file:read ephemeral might return "(mock file content)" or an empty string
so the user sees a “shape” of the result, not real I/O).file:write while typing, ephemeral mode says
"Cannot call file:write in ephemeral mode." which is safer than doing real writes.We previously described a pattern for each bridging function to define a metadata field, for example:
enum PreviewSafety {
Safe, // no side effects -> ephemeral calls are allowed
Forbid, // definitely has side effects -> ephemeral calls blocked
Stub, // ephemeral calls do a custom stub method
}
Under a default deny approach, if a function does not set preview_safety = Safe or Stub,
it becomes Forbid by default. This ensures no bridging function can slip through ephemeral mode
without an explicit “yes, we are safe” from the plugin developer.
math:plus, math:pow, math:sqrt, math:sum, etc.
→ Pure — ephemeral calls are fully safe.
math:rand → can be maybe pure if it uses a local RNG
that doesn't persist. But if it changes global seeds or a global RNG state,
ephemeral calls must be forbidden or stubbed.
math:srand → definitely Side-effectful (reseeding).
Mark as Forbid in ephemeral mode or provide a no-op Stub.
string:lower, string:upper, string:length, string:concat, string:to_string
→ Pure.
array:lower, array:upper, array:keys, array:length, array:join
→ Pure bridging logic.
array:filter, array:map, array:reduce, array:each →
Maybe because side effects can happen if the user’s callback is side-effectful.
The bridging itself is pure, but ephemeral calls might need to block or stub user callbacks that do I/O.
array:assoc, array:nth, array:range, array:tail
→ Pure.
file:read, file:write, file:stream
→ Side-effectful I/O.
We forbid ephemeral usage or create stubs that return dummy data if needed.
net:fetch, net:ping, net:stop, poll_events, block_until_no_tasks, embedded_ping
→ Side-effectful (networking, threads).
Typically Forbid ephemeral usage or stub it.
process:info
→ Potentially safe (it only reads system info).
We might mark it Safe if we consider reading system info “pure enough.”
process:check_tasks
→ Side-effectful, manipulates background tasks or polls them,
so ephemeral usage might be Forbid.
describe, it, expect_equal, expect_not_equal, etc.
→ Side-effectful, storing test state or printing.
Typically forbid ephemeral usage or stub with no test state changes.
event:timeout, event:interval, event:stop, event:check_tasks
→ Side-effectful scheduling tasks.
Ephemeral usage is normally Forbid or maybe Stub to produce dummy handles.
date:timestamp, date:ms, date:micro
→ Generally pure enough (just reading current time).
We can allow ephemeral calls that simply read the system clock in the ephemeral environment.
By default, we disallow ephemeral calls on all bridging functions except for those explicitly marked as PreviewSafety::Safe or Stub.
We use the math plugin first as a test bed, since math:plus and others are pure numeric transforms.
We’ll see ephemeral previews returning correct results with zero side effects.
Functions like math:srand or file:write remain disallowed until/unless we create stub logic for ephemeral usage or decide to forgo ephemeral calls entirely.
Then, one plugin at a time, we’ll document each bridging function as purely functional, side-effectful, or conditionally so. If side-effectful but we want ephemeral usage, the plugin must provide a stub function (e.g., returning a dummy success) for ephemeral mode. Otherwise ephemeral calls remain blocked.
This approach ensures minimal rewriting of existing code, and a safe default deny policy so no new bridging calls are inadvertently run with side effects in ephemeral.