Before diving off into real content, the name of my little π-calculus monstrosity has been chosen. As several people recommended in the comments, I’m going to go with Pica.
So, today, we’re going to take the dive, and start looking at some interesting semantics of the language. The goal of this is still to work on Pica’s type system: I want to work towards a first draft of what a process type will look like. But to figure out what a process type should look like, we need to think about how process abstractions will be defined and used in Pica, what control structures are going to look like. So that’s what I’m going to do in this post: ramble on a bit about what processes are really going to end up looking like. Just a warning: this post is definitely on the half-baked side – it’s much more of a train-of-thought/brainstorming thing than the things I usually post.
The overriding principle of Pica is making communication and synchronization explicit. After all, the basic problem that I’m hoping to address is the weakness of languages for dealing with the issues of distribution and concurrency, so those issues need to be brought up front and center. So the point of process typing is to allow us to express something about how a process is going to interact with the world around it in terms of communication.
Obviously, since Pica is based on π-calculus, the primary abstraction is going to be the process. Just about everything is ultimately going to reduce to process-based abstractions. What does this mean, in terms of some of the common programming language constructs?
- Procedure/method/function calls are not primitives. In almost every language that most of us are familiar with, the idea of the synchronous function call is a basic concept. But for Pica, it’s a derived abstraction. A function call with type (A×B)→C is going to become a process in Pica, which exports a channel of type (A×B×Channel[C]). To call the function, you’ll send a message to that exported channel, which will include the result channel. Instead of returning a result, the function will send the result to the final channel parameter. So function calls in Pica end up looking a lot like function calls in continuation passing style code in a functional language. This isn’t really changing anything about how function calls really work: normal function calls push the parameters plus a return address onto a stack. This is equivalent to that, expressed in terms of massages, and making the expected synchronization behavior around the call and return explicit. With calls expressed this way, we can still provide syntactic sugar to make it easy to do synchronous calls if we want to – but we can also make any call into an asynchronous operation, or separate the invoker of a call from the receiver of the result.
- Data types/classes aren’t really primitive. Most modern languages have a concept of object, which is basically a chunk of data (the object state) with associated code for accessing and manipulating the data (the object methods). In Pica, an object is just a process. The object state is the process state; the object methods will be defined in terms of the messages that can be sent to the process.
- Basic computational primitives are relatively unaffected. Things like conditionals, comparisons, and arithmetic are just below the level where the message-passing semantics has a huge impact.
- Control structure have an interesting dual-nature in a language like Pica. There’s obviously a need for basic controls like conditionals, loops, etc., which exist below the level of message passing – but there’s also obviously a need for controls to interact with message passing. The way I’m going to work around that is by separating the low-level in-process controls from the higher level inter-process controls.
That dual-level control structure is an interesting thing. On the low level, we want to be able to do things like simple conditionals and iteration without getting too involved in communication semantics. So we definitely want things like a basic “in/then/else” construct, and probably something like a “for” loop.
And there’s also an analogue of the basic control structures at the process level. But they need to fit into the basic concept and patterns of the process and messaging system. There are two basic constructs we need to capture at the process level: conditionals, and iteration.
What’s a conditional in terms of a process calculus like π? Suppose I’ve got a process, which has a port where it can receive a message. A conditional is something that allows the process to take different actions depending on the contents of the message. The natural way to do that given the structure we’ve been building is to steal another idea from functional languages: pattern matching. So we’ll allow message receive clauses to specify a pattern for the values to be received: a message receive clause can only receive a message whose payload matches the clause’s pattern.
Iteration is even easier. We’ve already seen the solution there: recursion, exactly the way that I demonstrated in some of the examples in earlier articles.