# Work in progress, specifications

##

Designing modules in the Yacas scripting language

###

Introduction

For any software project where the source code grows to a substantial amount of different modules, there needs to be a way to define interfaces between the modules, and a way to make sure the modules don't interact with the environment in an unintended way.

One hallmark of a mature programming language is that it supports modules, and a way to define its interface while hiding the internals of the module. This section describes the mechanisms for doing so in the Yacas scripting language.

###

Demonstration of the problem

Unintentional interactions between two modules typically happen when the two modules accidentally share a common "global" resource, and there should be a mechanism to guarantee that this will not happen.

The following piece of code is a little example that demonstrates the problem:

    SetExpand(fn_IsString) <-- [expand:=fn;];
    ram(x_IsList)_(expand != "") <-- ramlocal(x);
    expand:="";
    ramlocal(x) := Map(expand,{x});

This little bit of code defines a function `ram` that calls the function `Map`, passing the argument passed if it is a string, and if the function to be mapped was set with the `SetExpand` function. It contains the following flaws:

- `expand` is a global variable with a rather generic name, one that another module might decide to use.
- `ramlocal` was intended to be used from within this module only, and doesn't check for correctness of arguments (a small speed up optimization that can be used for routines that get called often). As it is, it can be called from other parts, or even the command line.
- the function `ramlocal` has one parameter, named `x`, which is also generic (and might be used in the expression passed in to the function), and `ramlocal` calls `Map`, which calls `Eval` on the arguments.

The above code can be entered into a file and loaded from the command line at leisure. Now, consider the following command line interaction after loading the file with the above code in it:

    In> ramlocal(a)
    In function "Length" :
    bad argument number 1 (counting from 1)
    Argument matrix[1] evaluated to a
    In function call  Length(a)
    CommandLine(1) : Argument is not a list

We called `ramlocal` here, which should not have been allowed.

    In> ram(a)
    Out> ram(a);

The function `ram` checks that the correct arguments are passed in and that `SetExpand` was called, so it will not evaluate if these requirements are not met.

Here are some lines showing the functionality of this code as it was intended to be used:

    In> SetExpand("Sin")
    Out> "Sin";
    In> ram({1,2,3})
    Out> {Sin(1),Sin(2),Sin(3)};

The following piece of code forces the functionality to break by passing in an expression containing the variable `x`, which is also used as a parameter name to `ramlocal`.

    In> ram({a,b,c})
    Out> {Sin(a),Sin(b),Sin(c)};
    In> ram({x,y,z})
    Out> {{Sin(x),Sin(y),Sin(z)},Sin(y),Sin(z)};

This result is obviously wrong, comparing it to the call above. The following shows that the global variable `expand` is exposed to its environment:

    In> expand
    Out> "Sin";

###

Declaring resources to be local to the module

The solution to the problem is `LocalSymbols`, which changes every symbol with a specified name to a unique name that could never be entered by the user on the command line and guarantees that it can never interact with the rest of the system. The following code snippet is the same as the above, with the correct use of `LocalSymbols`:

    LocalSymbols(x,expand,ramlocal) [
      SetExpand(fn_IsString) <-- [expand:=fn;];
      ram(x_IsList)_(expand != "") <-- ramlocal(x);
      expand:="";
      ramlocal(x) := Map(expand,{x});
    ];

This version of the same code declares the symbols `x`, `expand` and `ramlocal` to be local to this module.

With this the interaction becomes a little bit more predictable:

    In> ramlocal(a)
    Out> ramlocal(a);
    In> ram(a)
    Out> ram(a);
    In> SetExpand("Sin")
    Out> "Sin";
    In> ram({1,2,3})
    Out> {Sin(1),Sin(2),Sin(3)};
    In> ram({a,b,c})
    Out> {Sin(a),Sin(b),Sin(c)};
    In> ram({x,y,z})
    Out> {Sin(x),Sin(y),Sin(z)};
    In> expand
    Out> expand;

###

When to use and when not to use `LocalSymbols`

The `LocalSymbols` should ideally be used for every global variable, for functions that can only be useful within the module and thus should not be used by other parts of the system, and for local variables that run the risk of being passed into functions like `Eval`, `Apply`, `Map`, etcetera (functions that re-evaluate expressions).

A rigorous solution to this is to make all parameters to functions and global variables local symbols by default, but this might cause problems when this is not required, or even wanted, behaviour.

The system will never be able to second-guess which function calls can be exposed to the outside world, and which ones should stay local to the system. It also goes against a design rule of Yacas: everything is possible, but not obligatory. This is important at moments when functionality is not wanted, as it can be hard to disable functionality when the system does it automatically.

There are more caveats: if a local variable is made unique with `LocalSymbols`, other routines can not reach it by using the `UnFence` construct. This means that `LocalSymbols` is not always wanted.

Also, the entire expression on which the `LocalSymbols` command works is copied and modified before being evaluated, making loading time a little slower. This is not a big problem, because the speed hit is usually during calculations, not during loading, but it is best to keep this in mind and keep the code passed to `LocalSymbols` concise.

##

How Yacas Deals With Sets of Solutions

###

Introduction

##### (This is a draft)

Worries:

- need to change all code that uses Solve
- need a lot of changes in documentation
- arguments to solve are a bit more verbose than the previous version: Solve(`eq1,eq2`,vars) versus Solve(eq1 And eq2,vars). Suddenly things like Solve(leftlist==rightlist,vars) is not possible any more. This has to be done with extra commands (which is ok?). It can not stay the way it was, because lists now mean something else, a collection of disjunct solutions.

The difference between a problem stated and a solution given is a subtle one. From a mathematical standpoint,

    In> Integrate(x,0,B)Cos(x)
    Out> Sin(B);

And thus

    Integrate(x,0,B)Cos(x) == Sin(B)

is a true statement. Furthermore, the left hand side is mathematically equivalent to the right hand side. Working out the integration, to arrive at an expression that doesn't imply integration any more is generally perceived to be a more desirable result, even though the two sides are equivalent mathematically.

This implies that the statement of a set of equations declaring equalities is on a same footing as the resulting equations stating a solution:

a*x+b==c=>x==(c-b)/a.

If the value of x is needed, the right hand side is more desirable.

Viewed in this way, the responsibility of a `Solve` function could be to manipulate a set of equations in such a way that a certain piece of information can be pried from it (in this case the value of x==x(a,b,c).

A next step is to be able to use the result returned by a `Solve` operation.

###

Implementation Semantics of Solve in Yacas

Suppose there is a set of variables that has a specific combination of solutions and these solutions need to be filled in in an expression: the `Where` operator can be used for this:

    In> x^2+y^2 Where x==2 And y==3
    Out> 13;

Solve can return one such solution tuple, or a list of tuples. The list of equations can be passed in to Solve in exactly the same way. Thus:

    In> Solve(eq1,var)
    Out> a1==b1;
    In> Solve(eq1 And eq2 And eq3,varlist)
    Out> {a1==b1 And a2==b2,a1==b3 And a2==b4};

These equations can be seen as simple simplification rules, the left hand side showing the old value, and the right hand side showing the new value. Interpreted in that way, `Where` is a little simplifier for expressions, using values found by Solve.

Assigning values to the variables values globally can be handled with an expression like

    solns := Solve(equations,{var1,var2});
    {var1,var2} := Transpose({var1,var2} Where solns);

Multiple sets of values can be applied:

    In> x^2+y^2 Where {x==2 And y==2,x==3 And y==3}
    Out> {8,18};

This assigns the the variables lists of values. These variables can then be inserted into other expressions, where threading will fill in all the solutions, and return all possible answers.

Groups of equations can be combined, with

    Equations := EquationSet1 AddTo EquationSet2

or,

    Equations := Equations AddTo Solutions;

Where `Solutions` could have been returned by `Solve`. This last step makes explicit the fact that equations are on a same footing, mathematically, as solutions to equations, and are just another way of looking at a problem.

The equations returned can go farther in that multiple solutions can be returned: if the value of x is needed and the equation determining the value of x is x:=Abs(a), then a set of returned solutions could look like:

    Solutions := { a>=0 And x==a, a<0 And x== -a }

The semantics of this list is:

    either a >= 0 And x equals a, or
           a < 0 And x equals -a

When more information is published, for instance the value of a has been determined, the sequence for solving this can look like:

    In> Solve(a==2 AddTo Solutions,{x})
    Out> x==2;

The solution `a<0 And x==-a` can not be satisfied, and thus is removed from the list of solutions.

Introducing new information can then be done with the AddTo operator:

    In> Solutions2 := (a==2 AddTo Solutions);
    Out> { a==2 And a>=0 And x==a, a==2
      And a<0 And x==-a };

In the above case both solutions can not be true any more, and thus when passing this list to `Solve`:

    In> Solve(Solutions2,{x})
    Out> x==2;

`AddTo` combines multiple equations through a tensor-product like scheme:

    In> {A==2,c==d} AddTo {b==3 And  d==2}
    Out> {A==2 And b==3 And d==2,c==d
      And b==3 And d==2};
    In> {A==2,c==d} AddTo {b==3, d==2}
    Out> {A==2 And b==3,A==2 And d==2,c==d
      And b==3,c==d And d==2};

A list `a,b` means that a is a solution, OR b is a solution. AddTo then acts as a AND operation:

    (a or b) and (c or d) =>
    (a or b) Addto (c or d) =>
    (a and c) or (a and d) or (b and c) or (b and d)

`Solve` gathers information as a list of identities. The second argument is a hint as to what it needs to solve for. It can be a list of variables, but also "Ode" (to solve ordinary differential equations), "Trig" (to simplify for trigonometric identities), "Exp" to simplify for expressions of the form Exp(x), or "Logic" to simplify expressions containing logic. The "Logic" simplifier also should deal with a>2 And a<0 which it should be able to reduce to `False`.

`Solve` also leaves room for an 'assume' type mechanism, where the equations evolve to keep track of constraints. When for instance the equation x==Sin(y) is encountered, this might result in a solution set

    y == ArcSin(x) And x>=-1 And x <= 1

###

Use Case Scenarios

##### To be filled in