Electric Communities: E Programming Language 


The E Language Specification


This chapter describes the E language specification. Generally, E follows Java's language specifications, plus some additions.

Additional functionality for Java keywords. In E, you use regular Java statements such as implements and imports. Generally, the behavior of these keywords is the same as in Java.

The import statement has some additional functionality when used in E, in that it also imports the sealers (as well as the related classes and methods) for the messages in the E-classes and E-interfaces.

E reserved words. Besides the regular Java reserved words, the E runtime has its own reserved words.

E language elements. E also provides language elements that support the E runtime:


E reserved words


<- (send)

recipientEobject <- messageName (messageParameterList);

The send operator (<-), is the operator that sends E messages to an E-object. The messageName and messageParameterList take the same form as they do in a regular Java method call.

The recipientEobject denotes the E-object to which the message should be sent. This can be an E-object proper or a channel. If you already have a sealed message encapsulated in an envelope (that is, in an object of class RtEnvelope), you can send it with an expression of the form:

recipientEobject <- envelopeExpression;

where envelopeExpression is an expression whose result is the envelope you wish to send.

Alternatively, the recipient can be a variable of class RtEnvelope. In this case, the message is simply sealed in a new envelope which is stored in the variable, but is not sent anywhere.

Examples

Send a message myMessage to an E-object called otherGuy.

otherGuy <- myMessage ();

The E-object otherGuy has a corresponding E-method called myMessage, which it executes when it receives the message.

eclass otherGuyClassDefinition {
  .......
  myMessage();
  .......
}


& (distributor operator)

& channelName

The distributor operator (the ampersand symbol) obtains a channel variable's distributor (in order to send it a forward message, for example). The result is of class EDistributor and you can treat it as a normal value; for example, you can pass it as a message parameter.

You can only use this operator within the scope of the channel's initial declaration. You cannot use this operator on a channel which has been passed as the parameter of an E message or Java method call, or stored as an instance variable of an object other than that object itself.

All variables whose class is EObject (or a subclass of EObject) that have not been explicitly initialized in their declaration are implicitly initialized to a new instance of EChannel. Such channels are initially unforwarded (that is, they have not yet been given a destination E-object. See the forward statement later in this chapter for more information).

To forward a channel, send the forward message to the channel's associated distributor, including the destination E-object as a message parameter (see the following example).

Example

&sparkPlug <- forward(spark);

This example extracts the distributor for the channel sparkPlug, and sends the distributor a message forwarding it to spark.


eclass

modifier* eclass newEClassName 
  [ extends superEclassName ]
  [ implements interfaceList ]
  { eclassBody }

The eclass statement defines a new E-class, and is similar to Java's class statement.

An E-class can implement message protocols (called E-interfaces in this manual) as well as regular Java interfaces. You designate an E-interface with the einterface keyword. The optional interfaceList is a comma-separated list of interface and E-interface names.

An E-class can only extend another E-class. If it is not explicitly declared to extend another E-class, its immediate superclass is EObject. E-classes inherit E-interfaces, E-methods, regular Java methods, and instance variables from their superclasses.

Instance variables and Java methods in an E-class cannot be declared public; they can only be accessed from within the object itself. All references to the instance variables or Java methods of an E-class must be made relative to this or super, either explicitly or implicitly. This is because the reference may actually lead to a remote E-object in a different machine across a network connection; its instance variables and Java methods will not be directly accessible, even though you can send E-messages to it.

An eclass body can contain any of the statements allowed inside regular Java class body. In addition, it can contain either E-methods or an eforall declaration. These are described in the following subsections. Both Java methods and E-methods can use the E deferring statements (such as ewhen and ewhenever); these statements are described later in this chapter.

An E-class can have either an eforall or an emethod declaration; it cannot have both. However, it can still have Java methods in either situation.

The modifiers are optional and can be either the public, final or abstract Java modifiers.

Example

eclass EFileExampleLauncher implements ELaunchable
  {
    emethod go (RtEEnvironment env) {
     eclass body...}
  
}

emethod

emethod methodName (parameterList) {
  methodBody}

The emethod declaration defines an E-method for an E-class. An E-method is a block of code that is invoked when the E-object receives a corresponding message with that E-method name. An E-class must have an emethod declaration for each message it can receive. E-methods and their corresponding messages are named within the scope of the containing package (such as ec.e.run).

An E-method message parameter is typed in the same manner as a Java method parameter. The parameterList consists of a series of comma-separated entries of the form:

  parameterType parameterName

The body of the E-method follows the same format as a Java method body. Note that no return type is declared-since E-message sends are one-way, there is no return value. Also, since E-methods are not "called" by objects, they do not have access specifiers such as public or protected.

Java classes cannot contain E-methods, though they can send messages to E-objects.

Example

emethod DieRollEWhen(
  EInteger HisResult, EInteger HisKey, 
  EDistributor MyResult, EDistributor MyKey)
{
  DieRollEWhen code body

}

eforall

eforall (parameter
) {
  methodBody

}

The eforall declaration is an alternative to an emethod declaration. It receives all messages sent to instances of its class, but it is given these messages in sealed envelopes which it cannot open, since it lacks unsealers.

An eforall statement has a single parameter which must be of class RtEnvelope; this parameter contains the sealed message. Although the method cannot open the envelope, it can send it to other E-objects. You can use this feature to implement message "plumbing" of various sorts, such as forwarders, redirectors, and loggers.

Example

This example sends a message to an E-object newMiddleman containing an eforall. The eforall logs the message and then sends the sealed message on to another destination (theRealDestination) without ever "opening" it.

public eclass EMessagesender {
  EMiddleman newMiddleman = new EMiddleman();
  ....
  distributionList<-forward(newMiddleman);
  .....
}

eclass EMiddleman {
  eforall (RtEnvelope msg) {
    logMessage(msg);
    theRealDestination <- msg;
  } 
}


eif

eif (condition) {
  thenBlock
} [ eorif ( alternativeCondition ) {
  eorifBlock
} ]* [ else {
  elseBlock
} ]

The eif statement is similar to the Java if statement, and handles conditional execution clauses. However, it uses optimistic computation to determine the condition. This condition must be an EBoolean, though it can actually be a channel to an EBoolean whose value is undetermined at the time the eif statement is initially executed.

The eif statement is part of a special group of E statements that defer code execution until a value is furnished for a variable. These statements include eif, ewhen, ewhenever, and the ecatch portion of etry. These statements are described elsewhere in this chapter.

All other non-deferring statements in the code block which contains the eif statement (including statements which follow the eif in the source text), execute before the code blocks associated with the eif, regardless of when the value of condition is furnished.

An eif statement defers execution of its associated code blocks until condition furnishes a value. If there are no eorif clauses (documented in the following subsection), and condition is etrue, thenBlock executes. If condition is efalse, the elseBlock executes (if there is one). If no EBoolean is furnished for condition, neither block executes.

Using eif with Java code

An eif statement can only be used inside an E-class, although it does not need to exist inside an E-method. Note, however, that if you use eif inside a regular Java method, you cannot return a value for that method from within the eif statement. This is because the Java method will actually have returned before any of the blocks associated with the eif gets executed. You can use a return statement (without a return value) inside an eif to exit the eif conditional block.

An eif statement can produce results different from Java's if statement, even though the code may appear logically the same. See the example at the end of this section for more information.

Multiway-guarded eif statement

If the eif statement contains one or more eorif clauses, it becomes a multiway guarded eif statement. A multiway eif handles conditional cases that are evaluated concurrently, such as timeouts. This construct consists of an eif clause, followed by one or more eorif clauses and an optional else clause:

eif ( condition
 ) {
  thenBlock
} [ eorif ( alternativeCondition
 ) {
  eorifBlock
} ]* [ else {
  elseBlock
} ]

The condition associated with thenBlock and the various alternativeConditions associated with the eorifBlocks are evaluated concurrently. The first to result in etrue has its associated code block executed and prevents all the other code blocks from executing. The elseBlock executes only if all the conditions result in efalse.

In a multiway-guarded eif, only one block will ever execute, though none of them will if none of the conditions furnishes a value.

Examples

eif (Win) {
  WinnerCode

} else {
  LoserCode

}

In this example, WinnerCode executes if the value produced from Win is true. LoserCode executes if the value is false. Neither code block executes if there is no value.

Sample eif program

eif statements may produce results different from Java's if statements, even though the code may appear logically the same. The following code samples illustrate this idea.

This program uses eif. It outputs:

  1: a=5 b=6 v=10
  4: a=5 b=6 v=10
  5: a=125 b=126 v=1000
  2: a=5 b=6 v=1000
  3: a=25 b=26 v=100

public class ScopeDemoEif
{
  public static void main(String args[]) {
    DemoClass theInstance = new DemoClass();
    theInstance <- go();
  }

  static void print(String tag, int a, int b, int v) {
    System.out.println
      (tag + " a=" + a + " b=" + b + " v=" + v);
  }
}

eclass DemoClass {
  int v = 10;

  emethod go() {
    int a = 5;
    int b = 6;

    ScopeDemoEif.print("1:", a, b, v);
    eif (etrue) {
      ScopeDemoEif.print("2:", a, b, v);
      a = 25;
      b = 26;
      v = 100;
      ScopeDemoEif.print("3:", a, b, v);
    }
    ScopeDemoEif.print("4:", a, b, v);
    a = 125;
    b = 126;
    v = 1000;
    ScopeDemoEif.print("5:", a, b, v);
  }
}

Sample if program

This program uses if. It outputs:

  1: a=5 b=6 v=10
  2: a=5 b=6 v=10
  3: a=25 b=26 v=100
  4: a=25 b=26 v=100
  5: a=125 b=126 v=1000

public class ScopeDemoIf
{
  public static void main(String args[]) {
    DemoClass theInstance = new DemoClass();
    theInstance <- go();
  }

  static void print(String tag, int a, int b, int v) {
    System.out.println
      (tag + " a=" + a + " b=" + b + " v=" + v);
  }
}

eclass DemoClass {
  int v = 10;

  emethod go() {
    int a = 5;
    int b = 6;

    ScopeDemoIf.print("1:", a, b, v);
    if (true) {
      ScopeDemoIf.print("2:", a, b, v);
      a = 25;
      b = 26;
      v = 100;
      ScopeDemoIf.print("3:", a, b, v);
    }
    ScopeDemoIf.print("4:", a, b, v);
    a = 125;
    b = 126;
    v = 1000;
    ScopeDemoIf.print("5:", a, b, v);
  }
}

Sample eorif program

eif (AlmostAtLight) {
  SlowWayDown...

} eorif (StopLightBroken) {
  StopSign...

} eorif (StopLightYellow) {
  SpeedUp...

} eorif (StopLightRed) {
  Halt...

} else {
  ProceedAtSpeed...

}

This guarded eif construct simulates decision-making when approaching a traffic light.

The eorif guard clauses look for one of the following conditions to be true: AlmostAtLight, StopLightRed, StopLightBroken, StopLightYellow.

The first one that evaluates as true causes the appropriate code block to execute: either SlowWayDown, StopSign, SpeedUp, or Halt; the rest of the construct is then disregarded. If all the guard conditions evaluate as false, code block ProceedAtSpeed executes.


einterface

einterface einterfaceName 
  [ extends einterfaceList ] 
  { messageDeclarations }

The einterface statement encapsulates a set of abstract E-message declarations, in the same way that Java's interface statement encapsulates a set of abstract Java method declarations. It can optionally extend one or more other E-interfaces. The einterfaceList, if given, is a comma-separated list of E-interface names.

The body of the statement is a series of message declarations of the form:

   messageName ( parameters );

Message parameters are declared the same manner as a Java abstract method declaration.

There is no return type for a message declaration; since E-messages are one-way, there is no return value.

NOTE: Each E-class implicitly defines an E-interface of its own, which describes any messages the E-class implements that are not already declared in its implemented E-interfaces.

Example

einterface ProtocolA extends ProtoB, ProtoC {
  MessageA(int, EInteger);
  MessageB(EBoolean);
}


ethrow

ethrow expression;

The ethrow statement is similar to Java's throw statement, except that it throws an E-exception. expression is the E-exception that is thrown. See the information on the etry statement for a more detailed discussion of E's exception handling mechanism.

Example

ethrow new RtDirectoryFailure("Directory not available");


etry

etry {
  trycodeBlock
} [ ecatch (exceptionType exceptionVariable) {
  ecatchcodeBlock
} ]+
 

The etry statement is similar to Java's try statement. It contains one or more ecatch clauses that receive E-exceptions and determine what to do with them. These ecatch clauses collectively establish a new E-exception handling environment for the execution of trycodeBlock. Any E-exceptions which are thrown by executing trycodeBlock are caught here, except those that are caught inside another etry statement.

Just as the Java exception handling environment follows the flow of method call and return, the E-exception handling environment follows the flow of message sends. However, because E-message sends are one-way operations and E-method invocations are asynchronous, the behavior of E's exception mechanism differs significantly from Java's.

The exception handling environment established by an etry statement accompanies any messages sent by trycodeBlock into the E-methods that process those messages. It will continue to accompany any messages those E-methods send in turn, unless they establish new E-exception environments with etry statements of their own.

An E-exception environment persists until all messages within it have been processed, including any messages spawned by remote objects in response to messages received from the current environment. In other words, an E-exception handling environment is responsible for all computation initiated within its scope, even if remote objects are doing the computing.

Any time an E-exception is thrown (via the ethrow statement), the exception is caught by whatever E-exception handling environment is current for the E-method that is executing.

All other non-deferring statements in the code block which contains the etry, including statements which follow the etry in the source text, execute before the code blocks associated with the ecatch clauses, regardless of when any E-exception is thrown as a result of trycodeBlock.

Unlike Java's try statement, the etry statement cannot have a finally clause. Also, E does not require your E-methods to either catch or declare non-runtime E exceptions. Finally, E-exceptions (which are of class RtEException) are based on the Java RuntimeException class, not Exception, so they does not encapsulate a back stack trace.

Example

etry {
  env <- lookup("fred", &result);
} ecatch (RtDirectoryFailure excep) {
    recoverFromDirectoryFailure(excep);
  }


ewhen

ewhen expression ( type valueName ) {
  block
} [ eorwhen alternateExpression ( type valueName ) {
  alternateBlock
} ]*

An ewhen statement defers execution of the code it contains until an actual value is furnished from an E-object. Though similar conceptually to calling a Java method in that both obtain a value from an object, ewhen's implementation of optimistic programming make it unique to E; there is nothing like it in Java.

Since E-methods and E-object cannot return values themselves, you can use ewhen to get values or other data from other E-objects. This value can be of any Java type. This means you can use ewhen to obtain Java values from E-objects, and thus bridge your Java and E code. For example, you can use ewhen to get a Java integer value from an EInteger object, and add this value to another Java integer.

expression is an E-object, though this may actually be a channel to an E-object whose value is undetermined at the time the ewhen statement is initially executed. After this E-object furnishes a value, the ewhen code block can execute.

type valuename is the type and name of the value furnished by expression. Since E-objects cannot return their value like ordinary objects, you include a value method in the E-object that lets it return a Java type value. See the following section Getting Java values from E-objects with ewhen for more information.

An ewhen can only be used inside an E-class, although it does not need to exist inside an E-method. However, if you use ewhen inside a regular Java method, you cannot return a value for that method from within the ewhen statement. This is because the Java method will actually have returned before any of the blocks associated with the ewhen gets executed. You can use a return statement (without a return value) inside an ewhen to exit the ewhen block.

NOTE: The ewhen statement executes only the first time a value is furnished. To execute a block of code every time a value is furnished, use ewhenever.

The ewhen statement is part of a special group of E statements that defer code execution until a value is furnished for an E-object. These statements include eif, ewhen, ewhenever, and the ecatch portion of etry.

All other non-deferring statements in the code block which contains the ewhen (including statements which follow the ewhen in the source text) execute before the code blocks associated with the ewhen, regardless of when the value of expression is produced.

For an ewhen statement that contains no eorwhen clauses (see the following paragraphs for information on eorwhen), when expression reveals a value for valueName (which must be of type type), block executes.

Getting Java values from E-objects

An E-object responds to an ewhen statement by providing a value for itself. However, an E-object cannot return its value like an ordinary Java object. Instead, you include a method within the E-object to let it furnish a Java type value.

To do this, you provide the E-object with a value method. This is an ordinary zero-argument Java method which should return the E-object's value. An E-object can return a value of any Java type.

For example, E's own EInteger class, which provides an optimistic computation wrapper of an instance of Java int, has the following value method:

int value() { return(myValue); }

(myValue is an instance variable of EInteger which contains the int that the EInteger is wrapping).

To have an E-object to furnish a value, you must include this value method in its class definition. Although the value method is not mandatory, an ewhen statement cannot "access" an E-object that does not have one.

Closures

When your E program encounters a deferring statement like ewhen, it takes a snapshot of all the referenced local variables and parameters within the scope of that statement. This snapshot is called a closure. E saves this closure for whenever the ewhen needs it; the ewhen then uses the values in the closure when it executes. Each ewhen gets its own closure.

Multiway-guarded ewhen statement

If the ewhen statement contains one or more eorwhen clauses, it becomes a multiway guarded ewhen statement. This means that the expression associated with block and the various alternateExpressions associated with the alternateBlocks are evaluated concurrently. The multiway-guarded ewhen construct consists of an ewhen clause followed by one or more eorwhen clauses:

ewhen expression
 ( type valueName
 ) {
  block
} [ eorwhen alternateExpression
 ( type valueName
 ) {
  alternateBlock
} ]*

The first code block that is furnished a result has its associated code block executed and prevents all the other code blocks from executing. Only one block ever executes. If no values are furnished for any of the expressions, none of the code blocks execute.

Examples

emethod BakeCake (parameters) {
  Code1

  ewhen Bob (int SomeNumber)   {
    SomeEwhenCode

  }
  ewhen Sue (int OtherNumber) {
    OtherEwhenCode

  }
  Code2

}

In this example, Code1 and Code2 do not contain any ewhen, eif or etry statements. Computation proceeds in the following order:

  1. Code1 executes.
  2. The two ewhen statements generate messages to obtain values from Bob and Sue. At this time, SomeEwhenCode and OtherEwhenCode do not execute, even if the requested values are available immediately. Instead, computation continues with Code2.
  3. Code2 executes.
  4. If Bob or Sue (or both) have furnished values for SomeNumber or OtherNumber, respectively, SomeEwhenCode or OtherEwhenCode can execute. The order of execution of SomeEwhenCode and OtherEwhenCode depends solely upon which happens to obtain its argument value first.

If an eorwhen clause had been used in place of the second ewhen statement, the example would look like this:

emethod BakeCake (parameters) {
  Code1

  ewhen Bob (int SomeNumber)   {
    SomeEwhenCode

  } eorwhen Sue (int OtherNumber) {
    OtherEwhenCode

  }
  Code2

}

In this case the interpretation is very similar to that of the first example. However, if Bob reveals a value before Sue does, then SomeEwhenCode will be executed but OtherEwhenCode never will be. Conversely, if Sue is first to reveal a value, OtherEwhenCode executes to the exclusion of SomeEwhenCode.

An ewhen statement defines the lexical environment of the code associated with it, tying it to the value received for the argument. The values of variables visible to the code inside the ewhen statement is the same as if it that code had executed immediately. In either of the above examples, the lexical environment of SomeEwhenCode includes the value for SomeNumber received from Bob in the message set up by the ewhen statement. In addition, if SomeEwhenCode and OtherEwhenCode use variables that are manipulated by both Code1 and Code2, they see only the results produced by Code1.

Sample program

This program will output:

  1: a=5 b=6 v=10
  4: a=5 b=6 v=10
  5: a=125 b=126 v=1000
  2: a=5 b=47 v=1000
  3: a=25 b=26 v=100

import ec.e.lang.EInteger;

public class ScopeDemoEWhen
{
  public static void main(String args[]) {
    DemoClass theInstance = new DemoClass();
    theInstance <- goint();
  }

  static void print(String tag, int a, int b, int v) {
    System.out.println
      (tag + " a=" + a + " b=" + b + " v=" + v);
  }
}

eclass DemoClass {
  int v = 10;

  emethod goint() {
    int a = 5;
    int b = 6;
    EInteger num = new EInteger(47);

    ScopeDemoEWhen.print("1:", a, b, v);
    ewhen num (int b_value) {
      ScopeDemoEWhen.print("2:", a, b_value, v);
      a = 25;
      b_value = 26;
      v = 100;
      ScopeDemoEWhen.print("3:", a, b, v);
    }
    ScopeDemoEWhen.print("4:", a, b, v);
    a = 125;
    b = 126;
    v = 1000;
    ScopeDemoEWhen.print("5:", a, b, v);
  }
}


ewhenever

ewhenever expression ( type valueName ) {
  block
}

An ewhenever statement is an ewhen statement which executes its code block every time it receives a value for its argument.

Multiple executions occur when a channel is forwarded to multiple destinations, all of which furnish values for the ewhenever argument. An ewhen statement would execute block only once, the first time a value is furnished for expression.

The other difference between ewhen and ewhenever is that an ewhenever statement may not have any eorwhen clauses, since the mutual exclusion which eorwhen implies does not really make sense in the context of a statement which executes multiple times.

See the ewhen statement documentation for complete information about order of code execution, the lexical environment of block and so forth.

Like an ewhen, an ewhenever gets a snapshot of all its variables called a closure. However, an ewhenever's execution operates on a copy of this closure, not the actual closure itself. This ensures that the closure retains its original values for the next ewhenever execution. If your ewhenever statement modifies any of the values in the closure, those new values are not retained.

Be aware, however, that if your deferring statement references Java objects, the closure captures the reference to that object, not the value of the object itself. If your program subsequently modifies the object, the closure will reference that modified object, and the next ewhenever will execute using the new value.


forward

distributor <- forward( destinationEObject );

The forward message provides a channel's distributor with a destination. Once you have forwarded a channel, you cannot unforward it, though you may forward a channel more than once.

A channel itself is an E-object, derived from class EChannel (or one of its subclasses). In E, all E-object variables that are not explicitly initialized in their declarations are implicitly initialized to new instances of EChannel. These channels are initially unforwarded.

Every channel has a distributor, which can be obtained from the channel (in the channel's defining scope only) using the distributor operator, "&" (see the discussion of this operator at the beginning of this chapter). The distributor is an E-object of class EDistributor. To forward a channel, send a forward message to the channel's distributor.

When a channel is forwarded to an E-object, all messages sent to that channel are delivered to that E-object. You can even send messages to an unforwarded channel; these messages will be delivered later when the channel is eventually forwarded.

You can forward a channel to many recipients; all messages sent through that channel will be received by all the recipients. Messaging is asynchronous: when you forward a channel to a new recipient, it receives all the messages that have ever been sent through that channel.

Examples

The most common usage would result from a message send something like this:

obj <- req(parameters, &someChannel);

The receiving E-method would look like the following:

req(parameters, Edistributor resultDist) {
  ...do some stuff...

  resultDist <- forward(someResultObject);
  ...
}

A less common case is something like:

&someChannel <- forward(someObject);

This is used when you need to pass a channel to another object and want to control where the messages that object sends to that channel will go.


Copyright (c) 1996 Electric Communities. All rights reserved worldwide.
Most recent update: 7/19/96