1 RRLL: Actor Basic 2.0
Racket Rogue-Like Library: Actor Basic 2.0
1.1 Introduction
Actor Basic is a S-Expression language. The basic element of the Actor Basic language is a S-Expression list with abasic-lambda as its first element and it forms an Actor Basic program. The Racket representation of such program is then a procedure which accepts a single argument (the actor), passes it through the steps of the program which may modify it and eventually returns the final actor as updated by all the steps performed.
To create modules with Actor Basic programs, use the s-exp meta-language:
#lang s-exp rrll/abasic/standalone ...
This is a work-in-progress language focused on concepts found both in actor model, flow-oriented programming and strictly immutable data structures.
1.2 Concepts
This section clarifies various concepts used in the Actor Basic documentation. Such clarity is required as certain concepts are approached differently than the reader might be accustomed to.
Actor
An Actor is an encapsulation of unit of computation which can be programmed in imperative-like manner while not requiring shared mutable state. Actors can communicate only by message passing and can only update or directly query only their internal state.
Actor State
The state of each actor consists of two components. Firstly the current continuation - which can be seen as capturing the current position in time of this actor’s existence - and secondly the data structure containing such actor’s id, auxilliary data, incoming message queue and registered asynchronous message handlers.
The auxilliary data is just an immutable dictionary with symbols as keys and any values associated with those keys. These keys are refered to as actor variables.
Actor Step
A step is a transition from one actor state to the next one. At the end of each transaction the actor may suspend its execution or handle incoming messages.
The whole actor program is a sequence of steps that keep on creating new actor states as they are performed. Always the latest actor state is propagated into the actor’s containing actor space.
Semantically each step is a function with the following template:
(define ((step . args) act) expr ...)
If used without any arguments, no evaluation parentheses are required in the Actor Basic program:
... step ...
However if arguments are given, they have to be passed as with ordinary procedure application:
... (step 1 2 3) ...
In both cases the result of step evaluation is the next actor state of given actor.
Actor Expression
Any argument to actor steps or special forms is an actor expression.
Such expression can be a literal number, string or symbol.
If it is an identifier, it is assumed to be a procedure with the following template:
(define ((proc . args) act) expr ...)
It is similar to actor step, however it can return an arbitrary value and its result is used where the expression was used. It does not update the current actor state.
An expression can be a actor variable.
In order to evaluate some expression as a Racket expression, quasiquote must be used. Inside the quasiquote, unquote can be used to get back to Actor Basic expression evaluation.
(debug "~v ~v" $pos `(vec2-add ,$pos (vec2 1 0)))
This allows for arbitrary nesting of Actor Basic and Racket expressions.
Actor Variable
Actor variables are stored in the actor state auxilliary data dictionary. Each variable is represented by a single key which has to be a symbol?. The associated data can be anything.
In actor steps and expressions the variables can be read by using an identifier starting with $ followed by the variable name (key symbol).
If the auxilliary data contains a key 'some-var with value "Some value", the following expression returns "Some value":
$some-var
The variables can be updated via variable assignment step. The identifier for such step starts with $ followed by the variable name (the key symbol) with := appended to it. It is used as procedure setting such variable to a new value.
The following code updates the $some-var variable stored under the key 'some-var in the actor’s auxilliary data.
($some-var:= "Another one") After performing this step, the value stored under given key will be "Another one".
Actor Message
Actors can send and receive asynchronous messages (from their perspective). Each message contains the following fields:
code - a symbol? specifying the type of the message
data - a list? of arguments
sender - a reference to originating actor
optional reply queue
TODO: clarify reference (id, queue)
Actor Reference
Any actor can be referenced by its id number and command queue.
Actor Queue
A thread-safe queue that allows communication both between actors within the actor space and from/to the actor space.
Actors should wrap the queue usage and if there are no events in the queue, they should yield with the event that waits for the queue to become ready.
Actor Method
A procedure-like construct that is evaluated when given actor receives a message with given code. This evaluation behaves like level-triggered interrupt as any other code evaluation of the actor in question is suspended until the method finishes.
Actor Space
Contains references to current states of all actors.
The actor’s internal state is always updated in current-actorspace registry upon any update. This update is synchronous and it is safe to query the actorspace from other threads.
Actor Inheritance
#lang s-exp rrll/abasic/lang (define-actor (adder) (method (get-name) "Adder") (method (add a b) `(+ a b))) (define-actor (strange-adder (bias 0)) (adder) (method (add a b) `(+ a b bias)))
1.3 Data Structures
This section describes all the internal data structures used by Actor Basic.
1.3.1 Core Actor State and Reference
(require rrll/abasic/actor-struct) | package: rrll-abasic |
struct
(struct %actor (id data queue methods))
id : fixnum? data : (and/c immutable/c (hash/c symbol? any/c)) queue : %queue? methods : (and/c immutable/c (hash/c symbol? procedure?))
procedure
act : %actor? key : symbol? (%actor-get act key val) → any/c act : %actor? key : symbol? val : any/c
struct
(struct %ref (id queue))
id : fixnum? queue : %queue?
1.3.2 Actor State Interaction
(require rrll/abasic/actor) | package: rrll-abasic |
procedure
act : %actor? sym : symbol? proc : (->* () () #:rest list? (-> %actor? %actor?))
Must be used from within the method step form.
1.3.3 Actor Queue Implementation
(require rrll/abasic/queue) | package: rrll-abasic |
This module implements the Actor Basic queue concept that can be used for sending messages to/from actors in their space and also between actors within that space.
struct
(struct %queue (queue lock sema [active? #:mutable]))
queue : queue? lock : semaphore? sema : semaphore? active? : boolean?
The event returned should be used inside Actor Space environment if the queue is not ready.
Removes the next element from the queue and returns it.
1.3.4 Actor Events
(require abasic/events) |
This module provides convenient evt? wrappers to be used by actor space for synchonizing actors’ computation. Wrapping events as custom struct allows for better displaying in actor monitor.
All the events return the waiting actor’s id upon successfull synchronization.
struct
(struct %evt (evt actor-id kind label))
evt : evt? actor-id : fixnum? kind : symbol? label : (or/c #f string?)
1.3.5 Actor Message Struct
(require abasic/msg) |
struct
(struct %msg (code sender kws kw-args args return))
code : symbol? sender : (or/c %ref? #f) kws : (listof keyword?) kw-args : list? args : list? return : (or/c symbol? #f)
code - represents the message type
sender - originating actor (if applicable)
kws - list of keywords
kw-args - a list of keyword argument values
args - positional arguments
return - symbol with return method handler
No support for keyword arguments is provided.
1.3.6 Actor Steps
(require rrll/abasic/steps) | package: rrll-abasic |
This module covers the basic steps provided to Actor Basic programs.
1.3.7 Actor Expressions
(require rrll/abasic/exprs) | package: rrll-abasic |
This module contains basic procedures that return some arbirary value and not a new actor state. These include for example predicates or any other querying functions.
1.3.8 Actor Space and Time
(require rrll/abasic/space) | package: rrll-abasic |
This module provides the evaluation environment for interacting actors.
struct
(struct %space (last-id actors receiver printer))
last-id : fixnum?
actors :
(and/c (hash/c fixnum? %actor?) immutable?) receiver : (or/c log-receiver? #f) printer : (or/c (-> vector? void?) #f)
procedure
→
fixnum? (-> (or/c evt? #f)) evt? space-box : (box/c %space?)
proc :
(-> any/c ... (-> %actor? any)) arg : any
Returns three values:
an actor id
the actor procedure that performs its next step and yields event or false
the initial event - which is a wrapped always-evt.
procedure
((run-actor main [ #:space-box space-box #:monitor monitor]) arg ...) → void? main : (-> (arg any/c) ... (-> %actor? any)) space-box : (box/c %space?) = (box (make-%space)) monitor : (or/c (-> (box/c %space?) boolean?) #f) = #f arg : any/c
It automatically spawns the NSA actor.
1.4 Actor Basic Expander
(require rrll/abasic/expander) | package: rrll-abasic |
Grammar for Actor Basic programs is as follows:
| ‹program› | ::= | ( actor-lambda ‹maybe-name› ( ‹argdefs› ) ‹steps› ) |
| ‹maybe-name› | ::= | |
|
| | | ‹identifier› |
| ‹argdefs› | ::= | |
|
| | | ‹argdef› ‹argdefs› |
| ‹argdef› | ::= | ‹identifier› |
|
| | | ( ‹identifier› ‹racket-expr› ) |
|
| | | ‹kw› ‹identifier› |
|
| | | ‹kw› ( ‹identifier› ‹racket-expr› ) |
| ‹kw› | ::= | keyword? |
| ‹identifier› | ::= | valid Racket identifier? |
| ‹racket-expr› | ::= | valid Racket expression |
| ‹steps› | ::= | |
|
| | | ‹step› ‹steps› |
| ‹step› | ::= | Actor Basic step |
Actor Basic programs body is just a series of steps with each step conforming to the following grammar:
| ‹step› | ::= | ‹identifier› |
|
| | | ( spawn ‹identifier› ‹args› ) |
|
| | | ( loop ‹identifier› ‹steps› ) |
|
| | | ( ‹identifier› ‹args› ) |
|
| | | ( ‹setter› ‹expr› ) |
|
| | | ( method ( ‹identifier› ‹argdefs› ) ‹steps› ) |
|
| | | ( send ‹expr› ‹identifier› ‹args› ) |
|
| | | ( reply ‹identifier› ‹args› ) |
|
| | | ( respond ‹arg› ) |
|
| | | ( delete ‹variable› ) |
|
| | | ( atomic ‹steps› ) |
|
| | | ( when ‹expr› ‹steps› ) |
|
| | | ( register ‹identifier› ) |
|
| | | ( let ( ‹aletpairs› ) ‹steps› ) |
|
| | | ( case ‹expr› ‹stepcases› ) |
| ‹args› | ::= | |
|
| | | ‹arg› ‹args› |
| ‹arg› | ::= | ‹expr› |
|
| | | ‹kw› ‹expr› |
| ‹expr› | ::= | Actor Basic expression |
| ‹setter› | ::= | an identifier? starting with $ and ending with := like $id:= |
| ‹aletpairs› | ::= | |
|
| | | ‹aletpair› ‹aletpairs› |
| ‹aletpair› | ::= | ( ‹identifier› ‹expr› ) |
| ‹stepcases› | ::= | |
|
| | | ‹stepcase› ‹stepcases› |
| ‹stepcase› | ::= | ( ‹primitive› ‹steps› ) |
|
| | | ( ( ‹primitives› ) ‹steps› ) |
| ‹primitives› | ::= | |
|
| | | ‹primitive› ‹primitives› |
Actor Basic expressions use the following grammar:
| ‹expr› | ::= | ‹primitive› |
|
| | | ‹variable› |
|
| | | ( spawn ‹identifier› ‹args› ) |
|
| | | ‹identifier› |
|
| | | ( request ‹expr› ‹identifier› ‹args› ) |
|
| | | sender |
|
| | | sender/id |
|
| | | ‹racket-esc› |
|
| | | ( lookup ‹identifier› ) |
|
| | | ‹named-actor› |
| ‹primitive› | ::= | ‹number? literal› |
|
| | | ‹string? literal› |
|
| | | ‹boolean? literal› |
|
| | | open quote ‹identifier› close |
| ‹variable› | ::= | an identifier? starting with $ like $id |
| ‹racket-esc› | ::= | ( quasiquote ‹racket-esc-expr› ) |
|
| | | ` ‹racket-esc-expr› |
| ‹racket-esc-expr› | ::= | ( unquote ‹expr› ) |
|
| | | , ‹expr› |
|
| | | ‹racket-expr› |
|
| | | ( ‹racket-esc-expr› ‹maybe-racket-esc-expr› ) |
| ‹maybe-racket-esc-expr› | ::= | |
|
| | | ‹racket-esc-expr› |
| ‹named-actor› | ::= | identifier? starting with ! like !actor |