Skip to content

old‐sh‐sh Scopes

price edited this page Sep 29, 2024 · 1 revision

sl-sh scoping rules

Global/Namespace

A namespace is a named global scope, all namespaces are layered on top of the root namespace (which contains all the core symbols). The current namespace is the current global scope. A namespace can export symbols via the ns-export form and import another namespaces symbols via ns-import (no need to import the root namespace). Any binding created with the def form or one of it's derivatives (defn, defmacro) will be interned in the current namespace (note this is determined lexically so using def in a lambda will but the symbol in the namespace where the lambda is defined not where it is called).

See https://sstanfield.github.io/slsh/#Namespace%20forms-body for the namespace form docs.

Symbols in a namespace can be accessed from another namespace via a "long name", for instance the sym symbol in the example namespace can be accessed as example::sym from any namespace even if not imported.

NOTE: The symbols in a namespace can only be written to (or added) from within the namespace.

NOTE: Shell environment variables are not effected by namespace changes, just lisp symbols.

Lexical Scopes

Creating a lambda also creates a new lexical scope. Lambda are created via the fn primitive which is the basis of several macros (defn, varfn, let, lex, loop, etc). Anything that creates a new lexical scope ultimately does so via fn. The lambda will capture any variables in containing lexical scopes that it references (when the fn is evaluated). This allows lambdas to assess data that may not be visible to code that can access it's symbol allowing simple classes or other forms of data hiding/sharing to be implemented. The let macro is a wrapper around fn, creating something similar to

((fn (p1 p2 ... pn) (body)) v1 v2 ... vn)

If you are already in a lambda then using var to add a symbol to it's scope is probably better then a macro like let. In sl-sh the var form will add a symbol to the current lexical scope.

Note: Using def in a lambda will create a global binding in the current namespace- use var for local variable bindings.

Example of lambdas and lexical scopes:

; Define a symbol at the enclosing scope that will be assigned a lambda below.
; Even though it is created in an anonymous lambda it will keep captured bindings alive.
(fn (x y z)
  (var a 1)
  (defn some-fn (b c) (
    ; The body of some-fn will be able to access x y z and a if it references them (captures them) and always b c since they are local.
  ))

Dynamic Scope

Sl-sh also supports dynamic scoping via the dyn builtin. By creating a dynamic scope the dynamic binding(s) will take precedent over any global bindings while they are in scope. This applies to anything called indirectly as well (i.e. the *stdout* variable could be overwritten for all code called while dynamic binding is in force. Calls to dyn can be nested and binding will be replaced as inner dyns exit. Dynamic scope is intended for special cases and is not ergonomic to use for multiple bindings at once (would require nested dyn forms).

Example:

(defn test-dyn-fn () (print \"Print dyn out\"))
(dyn *stdout* (open \"/tmp/sl-sh.dyn.test\" :create :truncate) (do (test-dyn-fn)))
(test::assert-equal \"Print dyn out\" (read-line (open \"/tmp/sl-sh.dyn.test\" :read)))