Visibility — scopes, private, hidden

Nelumbo has two mechanisms for controlling what names are visible where: scope blocks using { } and visibility modifiers (private, hidden, visible). This page describes both.


Scope blocks — { }

A block introduces a lexical scope. Any declaration inside the block — imports, types, patterns, variables, rules, facts, tests — is local to the block. Once the closing } is reached, those names are no longer in scope.

{
   Aa :: Object
   private Aa ::= XXX
   Aa x
   x = XXX ? [(x=XXX)][..]
}

{
   Bb :: Object
   private Bb ::= XXX
   Bb x
   x = XXX ? [(x=XXX)][..]
}

The two blocks above (from scoping.nl) each define a type, a literal, a variable, and a test. None of them collide, even though both use the name XXX and both use x. The scopes are independent; the private modifier on each ::= keeps the XXX literal local to its block.

What scopes are good for

Custom scope types

Any type that specialises Namespace introduces a scope of its own — no explicit {Namespace} prefix is needed (and none is accepted). Declaring a functor private within such a type confines it to that namespace, exactly as private does inside a { ... } block.

{
   Aa :: Object
   private Aa ::= XXX
   Aa x
   x = XXX ? [(x=XXX)][..]
}

From scoping.nl. The private keeps XXX local to the block; outside the block the literal does not exist.


Visibility modifiers

Three modifiers control how widely a pattern is visible:

Modifier Scope of visibility
(none) — the default Exported from the module; imports see it
private Visible only within the module (or scope) where it is declared
hidden Visible for parsing but not for use at call sites — see below

The visible keyword is the complement of hidden; see the section on hidden declarations below.

private — module-internal

A private declaration is not exported. It is a detail of the module's implementation.

private Boolean ::= eq(<Literal>, <Literal>)  @org.modelingvalue.nelumbo.logic.Equal

From logic.nl. The eq predicate is the native literal-equality primitive; it is used by the rule l1 = l2 <=> eq(l1, l2) inside logic.nl but is not something callers should reach for directly — they should use =. (The general-case = operator on Object is bound to a separate, public native NIs.)

Other private patterns in the stdlib include add, mult, string_concat, string_length, integer_string. All of them are native-backed primitives that the module wraps in friendlier operators.

hidden — callable but not at call sites

hidden restricts visibility in a more nuanced way: a hidden hole is present for parsing but cannot be supplied at the call site; it has to come from another rule or declaration.

The hidden.nl example illustrates the mechanism:

{
  Integer ::= <hidden Integer> && <Integer> #35
  Integer ::= <visible Integer> & <Integer> #35

  hidden Integer x
  Integer y, z

  x & y = z  <=>  x * 100 + y = z
  && y = z   <=>  x & y = z

  && 11 = 2211 ? [(x=22)][..]
  x & 11 = 2211 ? [(x=22)][..]
}

Notes on this example:

Hidden declarations are how Nelumbo supports operators with implicit operands and similar DSL constructs. They are an advanced feature.


Interaction with imports

Imports honour visibility. A module sees:

Transitive imports behave transitively: if module A imports B, and B imports C, then A sees C's exported declarations (via B re-exporting its imports). The standard library relies on this: nelumbo.integers imports nelumbo.logic, so any module that imports integers also has access to the logic primitives without an explicit import.


Interaction with scopes

Visibility modifiers apply relative to the declaration's scope. A private pattern inside a { } block is private to that block; outside, it does not exist at all (because of the scope), so the modifier is equivalent to plain scoping in that case.

private at module top level is what matters most — that is the module's boundary between implementation and API.


See also