I took Prof. Gerald Jay Sussman's MIT 6.945 Large-scale Symbolic Systems (aka Adventures in Advanced Symbolic Programming) in spring 2019.
As part of the course, we were to propose and complete a project, implementing it in Scheme, utilizing the philosophy of the course, which was:
Concepts and techniques for the design and implementation of large software systems that can be adapted to uses not anticipated by the designer.
My draft proposal contains some interesting ideas, such as:
-
Concurrency Substrate: async/await and actors
-
Dataflow Language: implementing dataflow processes like those found in LabVIEW or VHDL
-
Teachable Actors: teaching actors new behavior via new message processors
-
2D Graphics the Way I Think: generate 2D graphics and animation declaratively
-
Scheme Type System: adding types to Scheme without them getting in the way
-
Differential Forms Library: build a library to handle differential form calculations
In the end, I chose to implement async/await and actors in MIT/GNU Scheme, as I felt it was one that could be built off of to complete some of the other projects, most of which needed concurrency.
The code for the project is found in concurrency.scm. The only dependency is MIT/GNU Scheme. The code is well documented and organized, so take a look! I think it is a relatively nice example of adding a useful feature like async/await to a programming language without editing the compiler or interpretor. In this case, async/await was built using a simple macro and thread-safe queues. Once you have async/await that executes on threads, actors are easily added to the system.
A small preview is below, which is the definition of the actor loop process that listens via blocking, which is intentional since every actor runs in its own thread, for new messages and then processes them.
;;; This procedure defines the actual actor process. This is what is launched as an
;;; asynchronous process. The actor-loop processes messages as they arive and then recursively
;;; evaluates to wait for the next message. This procedure handles messages global to all actors,
;;; such as the stop message.
(define (actor-loop state message-processors inbox)
(let* ((message (pop! inbox)) ;; Wait for a message to arrive.
(message-name (car message)) ;; Get the message's name.
(message-value (cadr message)) ;; Get the message's value.
(is-synchronous? (equal? 'sync (caddr message)))) ;; Check synchronous flag.
(cond ((stop-message? message) state) ;; Check for the stop message.
(else (let ((procedure (get-message-processor-procedure message-name
message-processors)))
(if (equal? 'no-message-processor procedure) ;; Check for a msg processor
(actor-loop state message-processors inbox) ;; Ignore the message
(actor-loop (procedure state message-value) ;; Process the message and
message-processors ;; update the state
inbox)))))))
The presentation copied below gives a good high-level overview of the work.