Thoughts on Spek: The usage of channels and ports, Part 2

I covered this a bit in my last post, but the Spek language that I’ve been slowly working on over the past few months is based on the Axum programming language originally developed by Microsoft about five years ago. I’ve been making changes to the Axum language’s grammar after spending a couple of weeks painstakingly trying to recreate it. The area I’m working to change at the moment are channels and ports.

To recap where we are: channel patterns are entirely out and channel functions are on holiday until further notice.

With channel patterns being the first thing to go, we find ourselves in need of changing the channel’s overall composition, or we’d fall into the trap that the Axum designers used channel patterns to avoid. If a developer doesn’t utilize the ports correctly based on the channel patterns that previous kept state of the channel, how would you expect another agent to make use of it’s data? Take for instance this channel definition found in the Axum Language Specification:

channel Channel_A
{
    input Pair<int,int> Add;
    input Pair<int,int> Divide;
    output int Result;
}

When is it ready for the actor servicing this channel to compute the result? We know that you would either have to use the Add or Divide input ports, but not both for how we intend to use it. How does the runtime know that? For all it cares, it would probably require all input ports set to be utilized before it considers it ready and shoves it off. This is the reason for the channel pattern. We would use the patterns to dictate the state of the channel so that the runtime would know that only a single input port being set was fine and made it ready.

This is no longer an option with channel patterns removed, and the channel port structure needs to change. They should define their own individual return values instead of returning through a singular output port.

I’m not saying the grammar will support this, but it’s one possible way to implement it:

public domain MyDomain
{
    public agent MyAgent: channel Channel_A
    {
        public int Add<int,int>(val1, val2)
        {
            var result = val1 + val2;
            return result;
        } 
    } 

    channel Channel_A 
    {
        port int Add<int,int>;
        port int Divide<int,int>;
    }
}

The channel has essentially provided what looks like an interface with properties on it for implementation which translates from the C# world just fine.

Channel patterns now gone and we’ve modified channel ports to define their own output ports, so how could we enforce a contract now? The below is one method that I’ve been thinking of, is to utilize contract validation attributes like so:

channel Channel_A 
{
    port int Add<int,int>;

    [Message(y, "The values passed would result in a DivideByZero exception")]
    port int Divide<int,int> { x => false, y => y >= 0 };
}

Notice that the Divide port has two set of predicate lambdas on it which would return a boolean. In the event that one of the lambdas comes back true, the related Message attribute would be returned as an ContractViolationException prior to the channel being dispatched to the intended Agent. Remember, the channel is a mailbox and there are two of them – one outgoing, one incoming.

  • In a perfect real-world example, if the address is found invalid as known by the local post office, they would simply return it instead of sending half way across the country for the remote post office to determine it’s incorrect.
  • In a current real-world example, think of all those wasted resources: the sorting, the gas, the space, and the man-hours it took to realize it’s bogus.
  • In the Tron-world, we know it’s invalid and we can save the effort.

This doesn’t help where the data being passed is a complex object, and that’s where the future conversation about channel functions and schemas come into play.