Developing concurrent programs with Pony

Primitives

Primitives are classes without fields. Only one instance exists for them, much like none and null in other languages. Listing 4 begins by declaring the methodless primitives Amber, Blue, Crimson, and Other symbolically as the paint types used. Line 6 combines the four primitives using type to create the Paint alias, Color.

In contrast, the Properties primitive group functions in the form of methods – more specifically, the two helper methods list() and name(). The first returns all the paints in a field; the second returns the name of the paint as a string for the primitive passed to it (x). The selection in line 13 uses the match expression to identify the paints. More composite data types can be found in the collections package in Pony's standard library, in the form of set and map.

Reference Capabilities

Reference capabilities (Table 2), which Pony uses to prevent data races, are a special feature that programmers use to avoid setting multiple pointers to a variable object with two or more actors.

Table 2

Reference Capabilities

Type

Sendable

Write Permission

Read Permission

iso

Yes*

Exclusive

Exclusive

trn

No

Exclusive

Shared

ref

No

Shared

Shared

val

Yes

No

Shared

box

No

No

Shared

tag

Yes

No

No

*Yes, but only after deleting the sender.

Using reference capabilities and dealing with the resulting effects can initially cause some difficulty; programmers will probably need a ramp-up period. The expression var picasso: Painter iso, for example, would declare the reference picasso, which points to an object of type Painter. Thanks to the iso capability, it receives exclusive write and read access for the object. Because picasso is the only reference to the object, it can be sent safely to another actor after picasso is deleted.

To write meaningful programs, Pony also needs the other capabilities from Table 2. Pony sends objects of the val capability to all actors, much like a broadcast, because these objects are immutable (read but no write permissions). The trn and box types provide for data processing in the actor. The ref capability acts as a standard for variables and behaves like a pointer in other programming languages; tag actors protect against direct access.

Communication in the actor model relies on calling behaviors. The Rust language also limits pointers to avoids data races. Its user rights [8] only allow a reference with exclusive write and read access for each object. Developers grant the exclusive right to write useful programs.

Actors

Actors orchestrate the Pony application. Once started, they wait for asynchronous messages, evaluate the data supplied in accordance with the integrated behaviors, and send the results to other actors. Listing 5 shows the Painter actor from the sample application that consumes paints and reports consumption to the Main actor. An actor is similar to a class but also saves the behaviors referred to earlier.

Listing 5

painter.pony

 

The constructor of the Painter actor accepts a reference from the Main actor in line 5 (the one from Listing 6, line 6) and stores it in the listener field. The history field (Listing 5, line 3) keeps a record of all Resource objects sent via paint. Lines 8 to 15 define the paint behavior. It also requires an object of type Resource. If the level of the resource in line 9 is greater than zero, the history field saves another reference when the push() method in line 10 is called. Because the res reference has exclusive rights (thanks to the iso capability), push() first uses consume res to delete res. The next line sends the output to the stats() method of line 17 by sending the reference stored in listener to the notify behavior of actor Main (Listing 6, line 12).

Listing 6

main.pony

 

Continuing with Listing 5, the stats() method iterates in lines 19 to 25 over all paints from the primitives in Listing 4 in a loop defined by the return value of the list() method from the same listing.

Internally, loops in Pony do not work directly with fields, but with their iterator objects. Listing 5 generates an object by appending it to .values() (line 21). For each primitive, the run-time variable x iterates across all received resources. Line 22 totals up the quantity of paint consumed using the level() method from Listing 3. The summation uses an if expression for filtering. For all non-matching primitives from the history, the else branch adds   to the sum.

Another decision also needs to be made: If the paint resource res in line 9 is empty, the program processes the else branch from line 12. The res variable has the iso reference capability to comply with the required write and read rights from the fill() method declaration, which is stated as ref (Listing 3). The call in Listing 5, line 13, thus increments the private _amount field by one. Then, paint sends res recursively to itself but deletes res once again in line 14 with the help of consume.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News