The case for CellSpeak
Another programming language... really.
read more..
Cells run in parallel,
on all cores,
on interconnected systems,
as one program.
CellSpeak Platform |
Message based | Parallel, out of the box |
The CellSpeak platform consists of a language, a virtual machine and packages. With CellSpeak you build applications as a collection of cells. Cells can be big or small and an application can be made of a few or of thousands of cells. Cells communicate via messages. Cells belonging to the same application can run distributed, on one, a few or on thousands of systems.
CellSpeak is a strongly typed modern language. CellSpeak is compiled to machine independant CellSpeak bytecode - compile once, run everywhere. But CellSpeak is also fast - before execution the bytecode is compiled to native code.
CellSpeak is designed from the ground up to interface with existing software.
Cells have no shared data, but communicate via messages. A message has a name and carries a payload. Cells react to messages through the use of message handlers. It is a simple, powerful and elegant mechanism.
Cells can run locally or remotely, transparent to the application. Because message switching in CellSpeak is very fast and efficient, applications can be built from cells at a granular level.
CellSpeak supports many protocols and can easily exchange messages with applications that use other protocols - the web browser, systems that have a RESTfull API etc.
Cells do not require any effort to run in parallel - cells run in parallel - period.
Integrates with C/C++ |
Program Architecture |
Fully Documented and Supported |
CellSpeak is fully integrated with compiled software libraries for which there is an ABI - Application Binary Interface - such as libraries written in C/C++. Classes and methods are directly callable from within CellSpeak.
This tight integration allows CellSpeak to use available libraries for a given system as is.
This also means that legacy applications can easily be converted to high performance and scalable CellSpeak applications, by adding a thin layer of CellSpeak code.
The basic architecture of a CellSpeak program is a collection of cells that work together. Cells are organised in a hierarchy. This allows for extra flexibility, as a cell can relay messages to its children cells - for example to distribute work over many worker cells, or to assign work to a specialised cell. Applications can scale simply by adding more cells, but a single cell can also do work for several applications at the same time.
In CellSpeak, building an application is like setting up an organisation by allocating roles and tasks to cells.
In CellSpeak much more of the effort required to build an application, can go into the application logic itself. Parallel execution, interoperability and scalability of the application are built-in from the start, reducing thereby significantly the amount of boilerplate code and testing that otherwise would be required.
CellSpeak is extensively documented and is fully supported - training, assistance with project implementation and customization can all be provided for, to get your organisation up to speed in no time.
Distributed | Compiler and Virtual Machine | Application Examples |
CellSpeak is written for a world where distributed applications are the norm.
With CellSpeak, applications can be distributed over hundreds or thousands of servers, and cells can be deployed in these parts of a network where they are the most efficient.
CellSpeak communicates with remote systems using standard secure protocols on top of which it runs an efficient message exchange protocol.
The CellSpeak compiler converts source code into bytecode, that is independent of the target processor architecture. The same bytecode will run on any machine for which there is a CellSpeak Virtual Machine.
The Virtual Machine compiles the bytecode to native instructions for the target processor architecture. The Virtual Machine then executes the native code for the cells of the application in parallel.
Distributed Control
Use all available cores and processing power optimally.
Large Scale Simulations
Create, run and update cells on as many servers as required.
Internet of Things
Edge-computing - distribute cells in the network where they are the most effective.
Interactive Graphics and Games
Manage interactions in complex dynamic worlds.
Data Mining
Search, analyze and combine results in parallel.
read more..
read more..
Design Cells | Send Messages | Handle Messages |
Each cell has a design. A design contains data, functions and message handlers. Only the cell itself has access to its data and functions. Message handlers are executed when a message is received.
-- We use a group for related definitions and code group HeavenlyBodies -- The design for a planet. This design has three parameters. design PlanetDesign(ansi Name, float Distance, float Mass) is -- Keep is a shorthand to keep parameters as local data. keep Name, Distance, Mass xyz Position, Speed -- A cell can have an optional constructor and destructor. constructor is -- some initialisation code ... end destructor is -- code executed when the cell is destroyed ... end -- a function with three parameters that returns a result of type xyz function Rotate(float t, xyz Speed) out xyz is ... end -- SayHello is a messagehandler. on SayHello(cell HeavenlyBody) do HeavenlyBody <- Greet("Hello from planet [Name]") end end
The example shows that a design is a collection of data, functions and message handlers. The order of these in the design is not important. The data that is defined at the highest level, i.e. outside the context off a function or a message handler, is permanent, meaning that it will persist as long as the cell exists.
Cells based on a particular design are created with the keyword create, example:
cell World = new PlanetDesign("World", 1.0, 1.0 )
Note that the type of a cell is always cell - irrespective of the design. After having done some initialisation the cell waits for incoming messages to act on.
Sending messages in CellSpeak is easy. Messages have a name and can have parameters. Sending a message is always asynchronous: control returns immediately and continues with the next statement in the code.
-- we create some planets using the PlanetDesign cell Mercurius = create PlanetDesign("Mercurius", 0.06, 0.39 ) cell Venus = create PlanetDesign("Venus", 0.82,0.72 ) cell Earth = create PlanetDesign("Earth", 1.0, 1.0 ) cell Mars = create PlanetDesign("Earth", 1.88 , 0.52) -- we can send a message as follows: Mercurius <- ChangeOrbit(Distance, Excentricity) -- we can send mutiple messages in one send instruction Mercurius <- ChangeOrbit(0.54), Title("Herald Of the Gods") -- we can send mutiple messages to several cells in one send instruction Mercurius, Venus, Earth <- ReportPosition, ChangeSpeed( NewSpeed ) -- Message names with whitespace and special characters must use double quotes "" Mercurius, Venus, Earth <- "Is there water on your surface ?"(MaxDepth) -- Request a delivery notification - use <+- instead of <- Venus <+- HowManyMoons -- Send a priority message - use <*- instead of <- Mars <*- ImpactPending(Curiosity) -- Send a message that only one cell, the first available, should handle - use <!- instead of <- Mars, Venus, Mercurius <!- EvolveLife
The combination of the message name and the parameter signature is used to select the message handler in the receiving cell. The selection process is efficient because it uses a hash-value computed at compile-time.
Messages are handled by a message handler. Message handlers start with the keyword on. The message handler is selected using the name and the parameter signature of the message.
-- The message handler for the ChangeOrbit message on ChangeOrbit(float DistanceFromSun) do -- If the distance is not valid, just yield - processing stops here DistanceFromSun < 0 ? yield -- Call a function CalculateOrbitParameters(DistanceFromSun) -- Reply to the sender that the orbit was changed sender <- OrbitChanged end
Sender is a keyword that refers to the sender of the message that is being handled.
Messages that belong logically together can be grouped in an interface
interface TcpIp -- TCP IP connection request on Connect(ansi Name, ansi Port) do Output <- Print("\nMaking a connect request") ConxMgrClass.ConnectRequest( sender,Name, Port ) end -- The conxmgr can be requested to listen to a port on Listen(ansi Port) do Output <- Print("\nMaking a listen request") ConxMgrClass.ListenRequest(sender, Port) end
The sender of a message to an interface uses the complete message name:
-- sending a connection request using the TcpIp interface system <- TcpIp.Connect("Apollo","2264")
Cell Hierarchy | Records | Vectors and Matrices |
In CellSpeak, cells that are created by a cell are children of that cell. In this way a design can define a hierarchy of cells. A car for example could be made of a cell for the body, a cell for the engine and four cells for the wheels. The body in its turn could have a cell for the doors, the windows, the seats and so forth. This allows to break down a complex organisation into smaller manageable parts.
This also allows the parent cell to delegate message handling to its children, in a way that is opaque to the outside world.
-- The design of a car could be as follows: design CarDesign is cell doors, engine, windows, ... engine = create EngineDesign ... -- CarDesign re-routes the start message to the engine on Start => engine <- (same) end -- The design of the engine could be: design EngineDesign is ... on Start do ... end end -- If the following is sent to a cell made from CarDesign: MyCar = create CarDesign MyCar <- Start -- The engine will handle the 'Start' message
Message re-routing is a flexible tool that allows for specialisation and load balancing.
A design of a cell can be simple or complex depending on the tasks of the cell. It is therefore helpful to be able to use data types inside the cell that allow for efficient and structured programming. Enter the record. A record is like a class in other languages but it is called a record, because the type class is used explicitely for external classes, i.e. classes in external libraries written in another language, e.g. C++.
Note that in CellSpeak any type - scalar, array, record - can have methods:
-- The following defines a type AxisType, derived from a vector type xyz, -- i.e. axis will inherit all methods and operations from xyz, -- and defines also some specific operations on the type type AxisType is xyz with function Flip is ... end function Length out float is ... end end
Records can inherit from other records. Records can also be used as parameters in messages to other cells.
-- A record to keep data of a planet type PlanetRecord is record ansi Name float Mass -- In Earth Masses float Distance -- From Sun, in AU = distance earth sun -- Some methods. function GetDistance out float is return Distance end function SetName(ansi NewName) is ... end end -- An array of planet records - pluto rehabilitated PlanetRecord PlanetList[] = [ ["Mercurius",0.06, 0.39],["Venus",0.82,0.72], ["Earth",1.0,1.0], ["Mars",0.11, 1.52], ["Jupiter",317.8,5.2], ["Saturn",95.2,9.54], ["Uranus",14.6, 19.22], ["Neptune",17.2,30.06],["Pluto",0.00218,39.54] ] -- a variable of type PlanetRecord PlanetRecord World -- we can easily assign records World = PlanetList[2] -- or call a method using familiar dot semantics var d = World.GetDistance(); -- We can define and initialize a record in one go var B = keep [Rectangle, MemberCount]
There is more to tell about records - see the documentation for details - for example when records are derived from another record, fields can be renamed in the derived record.
CellSpeak has no generalised pointers, but it does have pointers to records. Pointers to records allow to construct interesting datastructures with records, like linked lists or tree structures.
Many applications use vector and matrix datatypes. CellSpeak has therefore built-in support for these datatypes. Vectors can have two, three or four components that can be of type integer, float or double. Matrices can have sizes 2x2, 3x3 or 4x4, also with integer, float or double components.
The built-in instructions for operations on vectors and matrices make sure that these are executed efficiently on the target architecture.
The vector and matrix types in CellSpeak have generic names for their components that can also be renamed by an application.
-- Definition of complex numbers - vec2f is a vector consisting of two floats. -- We change the component names to r and i for the real and imaginary part. type complex is vec2f with rename c1 to r rename c2 to i end -- Define complex multiplication. -- The lines starting with $ are CellSpeak assembly operator * (complex a, complex b) out complex is $ V2F_MULCMPX %sp a b $ FCT_RETURN %sp size complex end -- We can now do a complex multiplication complex U = [3, 4], V = [5, 2], W W = U*V -- W = [15 - 8, 20 + 6] = [7,26]
CellSpeak also has instructions for working with NxM matrices, but for a discussion about this see the CellSpeak documentation.
Functions | Arrays | Other |
As in other languages, functions in CellSpeak are used to structure code in small, manageable pieces. In CellSpeak functions are first-class citizens and can, for example, be used as parameters or return values in function calls. Function definitions can also be nested in other functions.
Similar to data, functions can be constant functions or variable functions. The body of a constant function cannot be changed while the body of a variable function can be changed either by an assignment of another function or by an explicit body statement.
Functions can return multiple values.
Functions can derive their signatures from other functions.
Small functions can easily be written in one line using a lambda.
-- Definition of a constant function with two return parameters function GetPosition(ansi PlanetName) out xyz, int is ... end -- Definition of a variable function var function Movement(float time) out xyz is ... end -- The body of the function can be changed afterwards body Movement is ... end -- A small single expression function function(float x, float y) => x^2 + y^3
Arrays in CellSpeak group N elements of the same type, indexed from 0 to N-1.
Arrays carry size information, so working with arrays in loops is easy:
-- An array of planet records - pluto rehabilitated PlanetRecord PlanetList[] = [ ["Mercurius",0.06, 0.39],["Venus",0.82,0.72], [Earth",1.0,1.0], ["Mars",0.11, 1.52], ["Jupiter",317.8,5.2], ["Saturn",95.2,9.54], ["Uranus",14.6, 19.22], ["Neptune",17.2,30.06],["Pluto",0.00218,39.54] ] -- Looping through all the elements in an array for each Planet in PlanetList do Planet.Mass *= 2 end -- An index loop, i takes all values from 0 to N-1. for each index i in PlanetList do PlanetList[i].Mass = Mass * i end
In CellSpeak all arrays are dynamic, and to create an array you simly declare it and give it a size, which can be a constant or a variable. Arrays can also be created by initialisation.
Arrays are de-allocated automatically. In order to make this efficient - a program in CellSpeak can have a very large number of cells - a key strategy for the memory allocation in CellSpeak is the use of scratchpad memory. The use of scratchpad memory fits perfectly with the message based design of CellSpeak. Allocation and de-allocation to scratchpad memory is simple and very fast.
-- A simple function to demo array allocation. The function returns an array of integers.
function GetArrayOfSquares(int n) out int[] is
int Squares[n]
for each index i in Squares do
Squares[i] = i^2
end
return Squares
end
Arrays that are allocated outside message handlers and functions, i.e. at the highest level in the design of a cell, are, like other data at that level, permanent. They will exist for the lifetime of the cell or until explicitely de-allocated.
To wrap up this short discovery of CellSpeak, below you can find a list of some other features of CellSpeak. Most features are self-explanatory, but details can be found in the documentation.
-.--------------------------------------------------------------- There are four comment styles. Two line comment styles : // and -- and two block comment styles /* */ and -. .- use // and /* */ to comment-out code and use -- and -. .- to add textual comment to the program -.--------------------------------------------------------------- -- multiple assignments are useful for swaps without temps a,b = b,a -- also this style of assignment is possible a = b = c = 177 -- var lets you declare a variable without specifying the type. var a = 5, b = 9 var r = a,s = r*b,t = s+r,u = 2,v = 3,w = 7 -- Create a record with the value and name of two variables as its fields var B = keep [Rectangle, MemberCount] -- Create another variable of the same (unnamed) type through assignment.. var F = B -- ..or by simply copying the type var G like B -- Some simple variables ... float A[10] int i=5 A[i] = 3.14159 -- In a string, values between square brackets are formatted Window <- Print("At index [i] in A we find [ A[i]]") -- this results in the string: "At index 5 in A we find 3.14159" -- Explicit formatting can be added if necessary: Window <- Print("At index [i,%d] in A we find [ A[i],%6.3f]") -. Working with strings is easy ... .- utf8 Intro = "My name is" utf8 Name = "Ozymandias" utf8 Occupation = "king of kings" utf8 Motto = "look on my works, ye Mighty, and despair!" -- Simple.. utf8 First = "[Intro] [Name], [Occupation], [Motto]" -- also this is possible... utf8 Second = "" Second = Intro + " " + Name + ", " + Occupation + ", " + Motto
CellSpeak is a language with a clear concise syntax for better readability and expresiveness . If you want to dive deeper into what can be done with CellSpeak, have a look at the tutorial below, the documentation, or better still, install CellSpeak and start experimenting.
CellSpeak is powerful but not complicated.
A short guide to programming in CellSpeak.
read more..
Take a look at how a simple, but not trivial, CellSpeak application is made here.
More examples can be found here.
read more..