Workspace User Language
From EQUIS Lab Wiki
Note: This content does not apply to the new system, and will be revised.
The Workspace Model specifies refinements and evolutions that can affect the structure of a running application. What is not specified by the model, however, is how a developer writes programs that take advantage of these capabilities. A user language defines the programming constructs used to express a program in terms of the Workspace Model.
A prototype user language called fiia was created by Greg Phillips in Python. Is forms the basis for ongoing development of a user language for the .Net platform.
Parameter Passing
The Workspace Model specifies that "All values passed by workspace connectors must be either immutable or passed by value." This behaviour is easy to implement, but contrasts with the default behavior of many programming languages. For user languages to integrate cleanly with existing languages, many will need to support at least some form of pass-by-reference (byref).
Pass by Port Descriptor
One possible implementation of byref parameter passing is to pass a port descriptor. Such a descriptor would provide a component id and port name, which could be used to create a port to access the language-level parameter. The user language runtime would handle creation and destruction of the necessary ports and connectors.
- Why pass a port descriptor rather than a port id?
- Passing a port identifier requires that the port be created before the method is called. The descriptor may be passed multiple times or be stored unused; it seems reasonable only to create and connect the port when it is actually needed.
- What about crossing workspace boundaries? (Open Question)
- The Workspace Model disallows components from creating any connection other than synchronization across a Workspace boundary. Events produced by synchronized stores might include port descriptors from a foreign workspace. These foreign descriptors could not have methods invoked on them, but could be added to a synchronization group (types permitting) and could serve as opaque unique identifiers.
- Alternative to passing (unusable) port descriptors across a workspace boundary are:
- Report a runtime error when a descriptor is passed between workspaces.
- May be difficult to trace and correct, especially in untyped languages. Would require the programmer add an explicit copy operation or remove the byref parameter.
- Report a runtime error when a descriptor is passed by the subscription source of a store.
- May be too restrictive as many GUI event models include a sourceElement in events.
- Report a compile error when a descriptor could be passed by the subscription source of a store.
- Only possible in typed languages, may be too restrictive as many GUI event models include a sourceElement in events.
- Report a warning when a descriptor is passed.
- Transparently copy the object into the target workspace.
- Unintuitive because the behavior changes depending on whether the components are within the same workspace or not.
Initial C# (and other .Net) User Language
Note that unlike the above discussion, all non-components are currently passed by value.
using System; using System.Threading; /* === Changes from basic .Net === * All normal structs and classes will be passed by value between components. * Passing classes by value is identical to .Net Remoting, but differs from .Net * normal. I consider it a bug. Once everything else works, I will be looking at * possibilities to pass classes by reference. * * No class may extend MarshalByRefObject (except as the next rule). * * All classes that extend ContextBoundObject must be tagged as components. * Attempting to pass a reference to a non-component ContextBoundObject across * contexts will result in a runtime error. * * === ISSUES === * Multi-target call and subscription ports are provided by delegates. * To make the delegate asynchronous, tag it with Workspace.SubscrSource. * * Should it be possible to tag interfaces or references as subscription sources? * * [Workspace.SubscrSource] * public interface ICounterListener { ... } * * or: * * ... * class Counter ... { ... * [Workspace.SubscrSource] * public ICounterListener listener; * ... } * * or: * * ... * class Counter ... { ... * public Workspace.SubscrSource<ICounterListener> listener; * ... } */ namespace Example { /* Not a component: a distinct copy of this class will be accessible in each context */ class Utilities { static public void Log(String message) { Console.WriteLine(Thread.CurrentContext.ContextID + ": " + message); } } [Workspace.Store] class Counter : ContextBoundObject { private int x; public int Value { get { return x; } set { x = value; Utilities.Log("CTR value=" + value); if (OnChange != null) OnChange(value); } } public delegate void OnChangeDelegate(int value); /* This delegate is tagged as a subscription source. The Workspace runtime will * provide an efficient asynchronous implementation of the delegate. */ [Workspace.SubscrSource] public OnChangeDelegate OnChange; } [Workspace.Actor] class Program : ContextBoundObject { static void Main() { new Program().Run(); } void Run() { /* Create a new component of type Counter and attaches a call port */ Counter counter = new Counter(); /* The above is equivalent to the following Workspace operations: * * c1 := component id for (existing) "Program" * c2 := component id for "Counter" * p1 := call source id for "Program.counter" * q1 := call target id for "Counter" * k1 := call connector id for p1 -> q1 * * A := CreateCallSource(A, c1, p1, "counter") * A := CreateStore(A, Counter, w, c2); * A := CreateCallTarget(A, c2, q1, "Counter") * A := Attach(A, k1, p1) * A := Attach(A, k1, q1) */ /* Attach a new connector to the OnChange subscription source of the Counter. * The delegate code is executed in the context of _Counter_ rather than the * one belonging to Program. I have not yet found a fix. */ counter.OnChange += delegate(int value) { Utilities.Log("DG1 value=" + value); Changed("LI1", counter, value); }; /* The above is equivalent to the following Workspace operations: * Line 4: "Program.Changed" should be "Program.anon_delegate_1" * See BUG above for justification. * * c1 := component id for (existing) "Program" * c2 := component id for (existing) "Counter" * p2 := subscr source id for "Counter.OnChange" * q2 := subscr target id for "Program.Changed" * k2 := subscr connector id for p2 -> q2 * * A := CreateSubscrTarget(A, c1, q2, "Program") * A := CreateSubscrSource(A, c2, p2, "OnChange") * A := CreateSubscription(A, w, k2); * A := Attach(A, k2, p2) * A := Attach(A, k2, q2) */ /* Attach a new connector to the OnChange subscription source of the Counter */ counter.OnChange += delegate(int value) { Utilities.Log("DG2 value=" + value); Changed("LI2", counter, value); }; /* Perform a sequence of increments. */ for (int i = 0; i < 1; ++i) { int delay = 950; Utilities.Log("INC value=" + counter.Value + " running"); counter.Value = counter.Value + 1; Utilities.Log("INC value=" + counter.Value + " sleeping"); Thread.Sleep(delay); } Utilities.Log("--- Done, press any key to terminate ---"); Console.ReadLine(); } void Changed(String name, Counter counter, int value) { Utilities.Log(name + " value=" + value + " sleeping"); Thread.Sleep(500); Utilities.Log(name + " value=" + value + " done"); } } }