# Advanced concepts

This chapter covers advanced concepts of the writing system. The first subject is the exception model. The second subject covers some properties of the namesets in the context of the interpreter object. The thread sub-system is then described along with the synchronization mechanism. Finally, some notes related to the functional system are given at the end of this chapter.

## Exception

An exception is an unexpected change in the execution flow. The exception model is based on a mechanism which throws the exception to be caught by a handler. The mechanism is also designed to be compatible with the native "C++" implementation. Exception

### Throwing an exception

An exception is thrown with the reserved keyword `throw`. When an exception is thrown, the normal flow of execution is interrupted and an object used to carry the exception information is created. Such exception object is propagated backward in the call stack until an exception handler catch it.throw

if (not (number-p n)) (throw "type-error" "invalid object found" n)

The example above is the general form to throw an exception. The first argument is the the exception id. The second argument is the exception reason. The third argument is the exception object. The exception id and reason are always a string. The exception object can be any object which is carried by the exception. The reserved keyword `throw` accepts 0 or more arguments.

throw throw "type-error" throw "type-error" "invalid argument"

With 0 argument, the exception is thrown with the exception id set to "user-exception". With one argument, the argument is the exception id. With 2 arguments, the exception id and reason are set. Within a try block, an exception can be thrown again by using the exception object represented with the `what` symbol.what

try { ... } { println "exception caught and re-thrown" throw what }

### Exception handler

The special form `try` executes a form and catch an exception if one has been thrown. With one argument, the form is executed and the result is the result of the form execution unless an exception is caught. If an exception is caught, the result is the exception object. If the exception is a native one, the result is nil.try

try (+ 1 2) try (throw) try (throw "hello") try (throw "hello" "world") try (throw "hello" "world" "folks")

In its second form, the `try` reserved keyword can accept a second form which is executed when an exception is caught. When an exception is caught, a new nameset is created and the special symbol `what` is bounded with the exception object. In such environment, the exception can be evaluated.

### Symbol bound to the exception object

Symbol | Description
--- | ---
eid | Exception id
name | Exception file name
line | Exception line number
about | Exception extended reason
reason | Exception reason
object | Exception object

try (throw "hello") (eval what:eid) try (throw "hello" "world") (eval what:reason) try (throw "hello" "world" 2000) (eval what:object)

Exceptions are useful to notify abruptly that something went wrong. With an untyped language, it is also a convenient mechanism to abort an expression call if some arguments do not match the expected types. eid about reason

# protected factorial const fact (n) { if (not (integer-p n)) (throw "number-error" "invalid argument in fact") if (== n 0) 1 (* n (fact (- n 1))) } try (fact 5) 0 try (fact "hello") 0

## Nameset

A nameset is created with the reserved keyword `nameset`. Without argument, the `nameset` reserved keyword creates a nameset without setting its parent. With one argument, a nameset is created and the parent set with the argument.nameset

const nset (nameset) const nset (nameset ...)

### Default namesets

When a nameset is created, the symbol `.` is automatically created and bound to the newly created nameset. If a parent nameset exists, the symbol `..` is also automatically created. The use of the current nameset is a useful notation to resolve a particular name given a hierarchy of namesets....

trans a 1 # 1 block { trans a (+ a 1) # 2 println ..:a 1 # 1 } println a # 1

### Nameset and inheritance

When a nameset is set as the super object of an instance, some interesting results are obtained. Because symbols are resolved in the nameset hierarchy, there is no limitation to use a nameset to simulate a kind of multiple inheritance. The following example illustrates this point.

const cls (class) const ins (cls) const ins:super (nameset) const ins:super:value 2000 const ins:super:hello "hello world " println ins:hello ins:value # hello world 2000

## Delayed Evaluation

The engine provides a mechanism called delayed evaluation. Such mechanism permits the encapsulation of a form to be evaluated inside an object called a promise. Delayed evaluation promise

### Creating a promise

The reserved keyword `delay` creates a promise. When the promise is created, the associated object is not evaluated. This means that the promise evaluates to itself.delay

const a (delay (+ 1 2)) promise-p a # true

The previous example creates a promise and store the argument form. The form is not yet evaluated. As a consequence, the symbol `a` evaluates to the promise object.

### Forcing a promise

The reserved keyword `force` the evaluation of a promise. Once the promise has been forced, any further call will produce the same result. Note also that, at this stage, the promise evaluates to the evaluated form.force

trans y 3 const l ((lambda (x) (+ x y)) 1) assert 4 (force l) trans y 0 assert 4 (force l)

## Enumeration

Enumeration, that is, named constant bound to an object, can be declared with the reserved keyword `enum`. The enumeration is built with a list of literal and evaluated as is. enum

const e (enum E1 E2 E3) assert true (enum-p e)

The complete enumeration evaluates to an `Enum` object. Once built, enumeration item evaluates by literal and returns an `Item` object.Item

assert true (item-p e:E1) assert "Item" (e:E1:repr)

Items are comparable objects. Only items can be compared. For a given item, the source enumeration can be obtained with the `get-enum` method.get-enum

# check for item equality const i1 e:E1 const i2 e:E2 assert true (i1:== i1) assert false (== i1 i2) # get back the enumeration assert true (enum-p (i1:get-enum))

## Logger

The `Logger` class is a message logger that stores messages in a buffer with a level. The default level is the level 0. A negative level generally indicates a warning or an error message but this is just a convention which is not enforced by the class. A high level generally indicates a less important message. The messages are stored in a circular buffer. When the logger is full, a new message replace the oldest one. By default, the logger is initialized with a 256 messages capacity that can be re-sized.Logger

const log (Logger) assert true (logger-p log)

When a message is added, the message is stored with a time-stamp and a level. The time-stamp is used later to format a message. The `length` method returns the number of logged messages. The `get-message` method returns a message by index. Because the system operates with a circular buffer, the `get-message` method manages the indexes in such way that the old messages are accessible with the oldest index. For example, even after a buffer circulation, the index 0 will point to the oldest message. The `get-message-level` returns the message level and the `get-message-time` returns the message posted time.Logger message

const mesg (log:get-message 0)

In term of usage, the logger facility can be conveniently used with other derived classes. The standard i/o module provides several classes that permits to manage logging operations in a convenient way.

## Interpreter

The interpreter is by itself a special object with specialized methods which do not have equivalent using the standard notation. The interpreter is always referred with the special symbol `interp`. The following table is a summary of the symbols and methods bound to the interpreter. Interpreter object interp

### Interpreter built-in symbols

Symbol | Description
--- | ---
argv | Command arguments vector
os-name | Operating system name
os-type | Operating system type
version | Full version
program-name | Interpreter program name
major-version | Major version number
minor-version | Minor version number
patch-version | Patch version number
afnix-uri | Official uri name
load | Load a file and execute it
launch | Launch a normal thread
library | Load and initialize a library
set-absolute-precision | Set absolute precision
set-relative-precision | Set relative precision
get-absolute-precision | Get absolute precision
get-relative-precision | Get relative precision

### Arguments vector

The `interp:argv` qualified name evaluates to a vector of strings. Each argument is stored in the vector during the interpreter initialization. Arguments vector argv

zsh> axi hello world (axi) println (interp:argv:length) # 2 (axi) println (interp:argv:get 0) # hello

### Interpreter version

Several symbols can be used to track the interpreter version and the operating system. The full version is bound to the `interp:version` qualified name. The full version is composed of the major, minor and patch number. The operating system name is bound to the qualified name `interp:os-name`. The operating system type is bound to the `interp:os-type`. Interpreter version System name and type

println "major number : " interp:major-version println "minor number : " interp:minor-version println "patch number : " interp:patch-version println "version number : " interp:version println "system name : " interp:os-name println "system type : " interp:os-type println "official uri : " interp:afnix-uri

### File loading

The `interp:load` method loads and execute a file. The interpreter interactive command session is suspended during the execution of the file. In case of error or if an exception is raised, the file execution is terminated. The process used to load a file is governed by the file resolver. Without extension, a compiled file is searched first and if not found a source file is searched. load

### Library loading

The `interp:library` method loads and initializes a library. The interpreter maintains a list of opened library. Multiple execution of this method for the same library does nothing. The method returns the library object. library

interp:library "afnix-sys" println "random number: " (afnix:sys:get-random)

### Interpreter duplication

The interpreter can be duplicated with the help of the `dup` method. Without argument, a clone of the current interpreter is made and a terminal object is attached to it. When used in conjunction with the `roll` method, this approach permits to create an interactive interpreter. The `dup` method also accepts a terminal object. dup roll

# duplicate the interpreter const si (interp:dup) # change the primary prompt si:set-primary-prompt "(si)"

### Interpreter loop

The interpreter loop can be run with the `roll`. The loop operates by reading the interpreter input stream. If the interpreter has been cloned with the help of the `dup` method, this method provides a convenient way to operate in interactive mode. The method is not called loop because it is a reserved keyword and starting a loop is like having the ball rolling. Interpreter loop

# duplicate the interpreter const si (interp:dup) # loop with this interpreter si:roll

## Librarian object

A librarian file is a special file that acts as a containers for various files. A librarian file is created with the `axl` -- cross librarian --utility. Once a librarian file is created, it can be added to the interpreter resolver. The file access is later performed automatically by name with the standard interpreter `load` method. Librarian

### Creating a librarian

The `axl` utility is the preferred way to create a librarian. Given a set of files, `axl` combines them into a single one. axl

zsh: axl -h usage: axl [options] [files] [h] print this help message [v] print version information [c] create a new librarian [x] extract from the librarian [s] get file names from the librarian [t] report librarian contents [f] lib set the librarian file name

The `c` option creates a new librarian. The librarian file name is specified with the `f` option.

zsh: axl -c -f librarian.axl file-1.als file-2.als

The previous command combines file-1.als and file-2.als into a single file called librarian.axl . Note that any file can be included in a librarian.

### Using the librarian

Once a librarian is created, the interpreter `-i` option can be used to specify it. The `-i` option accepts either a directory name or a librarian file. Once the librarian has been opened, the interpreter `load` method can be used as usual.

zsh> axi -i librarian.axl (axi) interp:load "file-1.als" (axi) interp:load "file-2.als"

The librarian acts like a file archive. The interpreter file resolver takes care to extract the file from the librarian when the `load` method is invoked.

### Librarian contents

The `axl` utility provides the `-t` and `-s` options to look at the librarian contents. The `-s` option returns all file name in the librarian. The `-t` option returns a one line description for each file in the librarian.

zsh: axl -t -f librarian.axl -------- 1234 file-1.als -------- 5678 file-2.als

The one line report contains the file flags, the file size and the file name. The file flags are not used at this time. One possible use in the future is for example, an auto-load bit or any other useful things.

### Librarian extraction

The `-x` option permits to extract file from the librarian. Without any file argument, all files are extracted. With some file arguments, only those specified files are extracted.

zsh: axl -x -f librarian.axl zsh: axl -x -f librarian.axl file-1.als

## Librarian object

The `Librarian` object can be used as a convenient way to create a collection of files or to extract some of them.

### Output librarian

The `Librarian` object is a standard object. Its predicate is `librarian-p`. Without argument, a librarian is created in output mode. With a string argument, the librarian is opened in input mode, with the file name argument. The output mode is used to create a new librarian by adding file into it. The input mode is created to read file from the librarian.

# create a new librarian const lbr (Librarian) # add a file into it lbr:add "file-1.als" # write it lbr:write "librarian.axl"

The `add` method adds a new file into the librarian. The `write` method the full librarian as a single file those name is `write` method argument.

### Input librarian

With an argument, the librarian object is created in input mode. Once created, file can be read or extracted. The `length` method -- which also work with an output librarian -- returns the number of files in the librarian. The `exists-p` predicate returns true if the file name argument exists in the librarian. The `get-names` method returns a vector of file names in this librarian. The `extract` method returns an input stream object for the specific file name.

# open a librarian for reading const lbr (Librarian "librarian.axl") # get the number of files println (lbr:length) # extract the first file const is (lbr:extract "file-1.als") # is is an input stream - dump each line while (is:valid-p) (println (is:readln))

Most of the time, the librarian object is used to extract file dynamically. Because a librarian is mapped into the memory at the right offset, there is no worry to use big librarian, even for a small file. Note that any type of file can be used, text or binaries.

## File resolver

The file resolver is a special object used by the interpreter to resolve file path based on the search path. The resolver uses a mixed list of directories and librarian files in its search path. When a file path needs to be resolved, the search path is scanned until a matched is found. Because the librarian resolution is integrated inside the resolver, there is no need to worry about file extraction. That process is done automatically. The resolver can also be used to perform any kind of file path resolution. File resolver

### Resolver object

The resolver object is created without argument. The `add` method adds a directory path or a librarian file to the resolver. The `valid` method checks for the existence of a file. The `lookup` method returns an input stream object associated with the object.

# create a new resolver const rslv (Resolver) assert true (resolver-p rslv)

# add the local directory on the search path rslv:add "."

# check if file test.als exists # if this is ok - print its contents if (rslv:valid-p "test.als") { const is (rslv:lookup "test.als") while (is:valid-p) (println (is:readln)) }

## Thread operations

The interpreter is a multi-threaded engine with a native implementation of objects locking. A thread is started with the reserved keyword `launch`. The execution is completed when all threads have terminated. This means that the master thread (i.e the first thread) is suspended until all other threads have completed their execution.Thread launch

### Starting a thread

A thread is started with the reserved keyword `launch`. The form to execute in a thread is the argument. The simplest thread to execute is the `nil` thread.

launch (nil)

There exists an alternate mechanism to start a thread with the reserved keyword `launch` and a thread object. Such mechanism is used when using deferred thread object creation or a thread generator object known as a thread set. Thread set

### Thread object and result

When a thread terminate, the thread object holds the result of the last executed form. The thread object is returned by the `launch` command. The `thread-p` predicates returns `true` if the object is a thread descriptor.Thread object

const thr (launch (nil)) println (thread-p thr) # true

The thread result can be obtained with the help of the `result` method. Although the result can be accessed at any time, the returned value will be `nil` until the thread as completed its execution.

const thr (launch (nil)) println (thr:result) # nilp

Although the engine will ensure that the result is `nil` until the thread has completed its execution, it does not mean that it is a reliable approach to test until the result is not `nil`. The engine provides various mechanisms to synchronize a thread and eventually wait for its completion.

## Shared objects

The whole purpose of using a multi-threaded environment is to provide a concurrent execution with some shared variables. Although, several threads can execute concurrently without sharing data, the most common situation is that one or more global variable are accessed -- and even changed -- by one or more threads. Various scenarios are possible. For example, a variable is changed by one thread, the other thread just read its value. Another scenario is one read, multiple write, or even more complicated, multiple read and multiple write. In any case, the interpreter subsystem must ensure that each objects are in a good state when such operation do occur.Shared objects

The engine provides an automatic synchronization mechanism for global objects, where only one thread can modify an object, but several thread can read it. This mechanism known as read-write locking guarantees that there is only one writer, but eventually multiple reader. When a thread starts to modify an object, no other thread are allowed to read or write this object until the transaction has been completed. On the opposite, no thread is allowed to change (i.e. write) an object, until all thread which access (i.e. read) the object value have completed the transaction. Because a context switch can occur at any time, the object read-write locking will ensure a safe protection during each concurrent access.Locking

### Shared protection access

We illustrate the previous discussion with an interesting example and some variations around it. Let's consider a form which increase an integer object and another form which decrease the same integer object. If the integer is initialized to 0, and the two forms run in two separate threads, we might expect to see the value bounded by the time allocated for each thread. In other word, this simple example is a very good illustration of your machine scheduler.

# shared variable access const var 0 # increase method const incr nil { while true (println "increase: " (var:= (+ var 1))) } # decrease method const decr nil { while true (println "decrease: " (var:= (- var 1))) } # start both threads launch (decr) launch (incr)

In the previous example, `var` is initialized to 0. The `incr` thread increments `var` while the `decr` thread decrements `var`. Depending on the operating system, the result stays bounded within a certain range. The previous example can be changed by using the main thread or a third thread to print the variable value. The end result is the same, except that there is more threads competing for the shared variable.

# shared variable access const var 0 # incrementer, decrementer and printer const incr nil (while true (var:= (+ var 1))) const decr nil (while true (var:= (- var 1))) const prtv nil (while true (println "value = " var)) # start all threads launch (decr) launch (incr) launch (prtv)

## Synchronization

Although, there is an automatic synchronization mechanism for reading or writing an object, it is sometimes necessary to control the execution flow. There are basically two techniques to do so. First, protect a form from being executed by several threads. Second, wait for one or several threads to complete their task before going to the next execution step. Synchronization

### Form synchronization

The reserved keyword `sync` can be used to synchronize a form. When a form, is synchronized, the engine guarantees that only one thread will execute this form.

const print-message (code mesg) ( sync { errorln "error : " code errorln "message: " mesg } )

The previous example creates a gamma expression which make sure that both the error code and error message are printed in one group, when several threads call it.

### Thread completion

The other piece of synchronization is the thread completion indicator. The thread descriptor contains a method called `wait` which suspend the calling thread until the thread attached to the descriptor has been completed. If the thread is already completed, the method returns immediately. Thread completion

# simple flag const flag false # simple tester const ftest (bval) (flag:= bval) # run the thread and wait const thr (launch (ftest true)) thr:wait assert true flag

This example is taken from the test suites. It checks that a boolean variable is set when started in a thread. Note the use of the `wait` method to make sure the thread has completed before checking for the flag value. It is also worth to note that `wait` is one of the method which guarantees that a thread result is valid.

Another use of the `wait` method can be made with a vector of thread descriptors when one wants to wait until all of them have completed.

# shared vector of threads descriptors const thr-group (Vector) # wait until all threads in the group are finished const wait-all nil (for (thr) (thr-group) (thr:wait))

### Complete example

We illustrate the previous discussion with a complete example. The idea is to perform a matrix multiplication. A thread is launched when when multiplying one line with one column. The result is stored in the thread descriptor. A vector of thread descriptor is used to store the result.

# initialize the shared library interp:library "afnix-sys"

# shared vector of threads descriptors const thr-group (Vector) # waits until all threads in the group are finished const wait-all nil (for (thr) (thr-group) (thr:wait))

The group of threads is represented as a vector. Based on the the previous discussion, a simple loop that blocks until all threads are completed is designed as a simple gamma expression.

# initializes a matrix with random numbers const init-matrix (n) { trans i (Integer 0) const m (Vector) do { trans v (m:add (Vector)) trans j (Integer) do { v:add (afnix:sys:get-random) } (< (j:++) n) } (< (i:++) n) eval m }

The matrix initialization is quite straightforward. The matrix is represented as a vector of lines. Each line is also a vector of random integer number. It is here worth to note that the standard math module provides a native implementation of real matrix.

# this procedure multiply one line with one column const mult-line-column (u v) { assert (u:length) (v:length) trans result 0 for (x y) (u v) (result:+= (* x y)) eval result }

# this procedure multiply two vectors assuming one # is a line and one is a column from the matrix const mult-matrix (mx my) { for (lv) (mx) { assert true (vector-p lv) for (cv) (my) { assert true (vector-p cv) thr-group:add (launch (mult-line-column lv cv)) } } }

The matrix vector multiplication is at the heart of the example. Each line-column multiplication is started into a thread and the thread object is placed into the thread group vector.

# check for some arguments # note the use of errorln method if (== 0 (interp:argv:length)) { errorln "usage: axi 0607.als size" afnix:sys:exit 1 }

# get the integer and multiply const n (Integer (interp:argv:get 0)) mult-matrix (init-matrix n) (init-matrix n) # wait for all threads to complete wait-all # make sure we have the right number assert (* n n) (thr-group:length)

The main execution is started with the matrix size as the first argument. Two random matrices are then created and the multi-threaded multiplication is launched. The main thread is blocked until all threads in the thread group are completed.

### Condition variable

A condition variable is another mechanism to synchronize several threads. A condition variable is modeled with the `Condvar` object. At construction, the condition variable is initialized to `false`. A thread calling the `wait` method will block until the condition becomes `true`. The `mark` method can be used by a thread to change the state of a condition variable and eventually awake some threads which are blocked on it. The following example shows how the main thread blocks until another change the state of the condition. Condition variable

# create a condition variable const cv (Condvar)

# this function runs in a thread - does some # computation and mark the condition variable const do-something nil { # do some computation .... # mark the condition cv:mark }

# start some computation in a thread launch (do-something)

# block until the condition is changed cv:wait-unlock

# continue here ...

In this example, the condition variable is created at the beginning. The thread is started and the main thread blocks until the thread change the state of the condition variable. It is important to note the use of the `wait-unlock` method. When the main thread is re-started (after the condition variable has been marked), the main thread owns the lock associated with the condition variable. The `wait-unlock` method unlocks that lock when the main thread is restarted. Note also that the `wait-unlock` method reset the condition variable. if the `wait` method was used instead of `wait-unlock` the lock would still be owned by the main thread. Any attempt by other thread to call the mark method would result in the calling thread to block until the lock is released.Condvar

The `Condvar` class has several methods which can be used to control the behavior of the condition variable. Most of them are related to lock control. The `reset` method reset the condition variable. The `lock` and `unlock` control the condition variable locking. The `mark`, `wait` and `wait-unlock` method controls the synchronization among several threads.

## Function expression

A lambda expression or a gamma expression can be seen like a function object with no name. During the evaluation process, the expression object is evaluated as well as the arguments -- from left to right -- and a result is produced by applying those arguments to the function object. An expression can be built dynamically as part of the evaluation process.

(axi) println ((lambda (n) (+n 1)) 1) 2

The difference between a lambda expression and a gamma expression is only in the nameset binding during the evaluation process. The lambda expression nameset is linked with the calling one, while the gamma expression nameset is linked with the top level nameset. The use of gamma expression is particularly interesting with recursive functions as it can generate a significant execution speedup. The previous example will behaves the same with a gamma expression.Function expression

(axi) println ((gamma (n) (+n 1)) 1) 2

### Self reference

When combining a function expression with recursion, the need for the function to call itself is becoming a problem since that function expression does not have a name. For this reason, the writing system provides the reserved keyword `self` that is a reference to the function expression. We illustrate this capability with the well-known factorial expression written in pure functional style. Self reference self

(axi) println ((gamma (n) (if (<= n 1) 1 (* n (self (- n 1))))) 5) 120

The use of a gamma expression versus a lambda expression is a matter of speed. Since the gamma expression does not have free variables, the symbol resolution is not a concern here.

### Closed variables

One of the writing system characteristic is the treatment of free variables. A variable is said to be free if it is not bound in the expression environment or its children at the time of the symbol resolution. For example, the expression `((lambda (n) (+ n x)) 1)` computes the sum of the argument `n` with the free variable `x`. The evaluation will succeeds if `x` is defined in one of the parent environment. Actually this example can also illustrates the difference between a lambda expression and a gamma expression. Let's consider the following forms.Closed variables

trans x 1 const do-print nil { trans x 2 println ((lambda (n) (+ n x)) 1) }

The gamma expression `do-print` will produce `3` since it sums the argument `n` bound to 1, with the free variable `x` which is defined in the calling environment as `2`. Now if we rewrite the previous example with a gamma expression the result will be one, since the expression parent will be the top level environment that defines `x` as 1.

trans x 1 const do-print nil { trans x 2 println ((gamma (n) (+ n x)) 1) }

With this example, it is easy to see that there is a need to be able to determine a particular symbol value during the expression construction. Doing so is called closing a variable. Closing a variable is a mechanism that binds into the expression a particular symbol with a value and such symbol is called a closed variable, since its value is closed under the current environment evaluation. For example, the previous example can be rewritten to close the symbol `x`.

trans x 1 const do-print nil { trans x 2 println ((gamma (n) (x) (+ n x)) 1) }

Note that the list of closed variable immediately follow the argument list. In this particular case, the function `do-print` will print `3` since `x` has been closed with the value `2` has defined in the function `do-print`.

### Dynamic binding

Because there is a dynamic binding symbol resolution, it is possible to have under some circumstances a free or closed variable. This kind of situation can happen when a particular symbol is defined under a condition.Dynamic binding

lambda (n) { if (<= n 1) (trans x 1) println (+ n x) }

With this example, the symbol `x` is a free variable if the argument `n` is greater than 1. While this mechanism can be powerful, extreme caution should be made when using such feature.

### Lexical and qualified names

The basic forms elements are the lexical and qualified names. Lexical and qualified names are constructed by the parser. Although the evaluation process make that lexical object transparent, it is possible to manipulate them directly.Lexical name Qualified name

(axi) const sym (protect lex) (axi) println (sym:repr) Lexical

In this example, the `protect` reserved keyword is used to avoid the evaluation of the lexical object named `lex`. Therefore the symbol `sym` refers to a lexical object. Since a lexical -- and a qualified -- object is a also a literal object, the `println` reserved function will work and print the object name. In fact, a literal object provides the `to-string` method that returns the string representation of a literal object. protect

(axi) const sym (protect lex) (axi) println (sym:to-string) lex

### Symbol and argument access

Each nameset maintains a table of symbols. A symbol is a binding between a name and an object. Eventually, the symbol carries the `const` flag. During the lexical evaluation process, the lexical object tries to find an object in the nameset hierarchy. Such object can be either a symbol or an argument. Again, this process is transparent, but can be controlled manually. Both lexical and qualified named object have the `map` method that returns the first object associated in the nameset hierarchy.Symbol access

(axi) const obj 0 (axi) const lex (protect obj) (axi) const sym (lex:map) (axi) println (sym:repr) Symbol

A symbol is also a literal object, so the `to-string` and `to-literal` methods will return the symbol name. Symbol methods are provided to access or modify the symbol values. It is also possible to change the `const` symbol flag with the `set-const` method.

(axi) println (sym:get-const) true (axi) println (sym:get-object) 0 (axi) sym:set-object true (axi) println (sym:get-object) true

A symbol name cannot be modified, since the name must be synchronized with the nameset association. On the other hand, a symbol can be explicitly constructed. As any object, the `=` operator can be used to assign a symbol value. The operator will behaves like the `set-object` method.

(axi) const sym (Symbol "symbol") (axi) println sym symbol (axi) sym:= 0 (axi) println (eval sym) 0

### Closure

As an object, the `Closure` can be manipulated outside the traditional declarative way. A closure is a special object that holds an argument list, a set of closed variables and a form to execute. The mechanic of a closure evaluation has been described earlier. What we are interested here is the ability to manipulate a closure as an object and eventually modify it. Note that by default a closure is constructed as a lambda expression. With a boolean argument set to true the same result is obtained. With false, a gamma expression is created.

(axi) const f (Closure) (axi) println (closure-p f) true

This example creates an empty closure. The default closure is equivalent to the `trans f nil nil`. The same can be obtained with `const f (Closure true)`. For a gamma expression, the following forms are equivalent, `const f (Closure false)` and `const f nil nil`. Remember that it is `trans` and `const` that differentiate between a lambda and a gamma expression. Once the closure object is defined, the `set-form` method can be used to bind a form.

# the simple way trans f nil (println "hello world") # the complex way const f (Closure) f:set-form (protect (println "hello world"))

There are numerous situations where it is desirable to mute dynamically a closure expression. The simplest one is the closure that mute itself based on some context. With the use of `self`, a new form can be set to the one that is executed. Another use is a mechanism call advice, where some new computation are inserted prior the closure execution. Note that appending to a closure can lead to some strange results if the existing closure expression uses return special forms. In a multi-threaded environment, the ability to change a closure expression is particularly handy. For example a special thread could be used to monitor some context. When a particular situation develops, that threads might trigger some closure expression changes. Note that changing a closure expression does not affect the one that is executed. If such change occurs during a recursive call, that change is seen only at the next call.advice