Split out effect-handling library.

This commit is contained in:
Tony Garnock-Jones 2016-06-08 13:52:32 -04:00
parent e74f6ae7e5
commit 2a218dd0a6
2 changed files with 237 additions and 107 deletions

View File

@ -39,6 +39,7 @@
(require "support/struct.rkt")
(require "pretty.rkt")
(require "treap.rkt")
(require "effect.rkt")
(define&provide-dsl-helper-syntaxes "state/until/forever form"
[on
@ -82,10 +83,10 @@
;; side-effects.
;; An Instruction is one of
;; - (patch-instruction Patch (Void -> Instruction))
;; - (action-instruction Action (Void -> Instruction))
;; - (patch-instruction Patch)
;; - (action-instruction Action)
;; - (return-instruction (Option (Listof Any)))
;; - (spawn-instruction LinkageKind (Symbol Symbol -> Spawn) (Void -> Instruction))
;; - (spawn-instruction LinkageKind (Symbol Symbol -> Spawn))
;; - (script-complete-instruction Variables)
;; and represents a side-effect for an actor to take in its
;; interactions with the outside world.
@ -113,10 +114,10 @@
;; (Background is done differently, with a new continuation for the
;; background script, and a self-send to activate it. (TODO))
;;
(struct patch-instruction (patch k) #:transparent)
(struct action-instruction (action k) #:transparent)
(struct patch-instruction (patch) #:transparent)
(struct action-instruction (action) #:transparent)
(struct return-instruction (result-values) #:transparent)
(struct spawn-instruction (linkage-kind action-fn k) #:transparent)
(struct spawn-instruction (linkage-kind action-fn) #:transparent)
(struct script-complete-instruction (variables) #:transparent)
;; An ActorState is an (actor-state ... as below), describing the
@ -183,46 +184,25 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Producing Instruction side-effects
(define prompt (make-continuation-prompt-tag 'syndicate-hll))
(define syndicate-tag (make-effect-tag 'syndicate))
(define (syndicate-actor-prompt-tag-installed?)
(continuation-prompt-available? prompt))
(effect-available? syndicate-tag))
;; (Any ... -> Nothing) -> (Any ... -> Instruction)
(define (reply-to k)
(lambda reply-values
(call-with-continuation-prompt (lambda ()
(apply k reply-values)
(error 'reply-to "Script returned directly"))
prompt)))
;; (-> Instruction) -> Nothing
(define (call-in-raw-context/abort proc)
(abort-current-continuation prompt proc))
;; ((Any ... -> Instruction) -> Instruction)
(define (call-in-raw-context proc)
(when (not (syndicate-actor-prompt-tag-installed?))
(error 'call-in-raw-context
"Attempt to invoke imperative Syndicate actor action outside actor context."))
(call-with-composable-continuation
(lambda (k) (abort-current-continuation prompt (lambda () (proc (reply-to k)))))
prompt))
(define do! (perform syndicate-tag))
(define do/abort! (perform/abort syndicate-tag))
;; Returns void
(define (assert! P #:meta-level [meta-level 0])
(call-in-raw-context
(lambda (k) (patch-instruction (core:assert P #:meta-level meta-level) k))))
(do! (patch-instruction (core:assert P #:meta-level meta-level))))
;; Returns void
(define (retract! P #:meta-level [meta-level 0])
(call-in-raw-context
(lambda (k) (patch-instruction (retract P #:meta-level meta-level) k))))
(do! (patch-instruction (retract P #:meta-level meta-level))))
;; Returns void
(define (patch! p)
(call-in-raw-context
(lambda (k) (patch-instruction p k))))
(do! (patch-instruction p)))
;; Returns void
(define (send! M #:meta-level [meta-level 0])
@ -230,25 +210,21 @@
;; Returns void
(define (perform-core-action! A)
(call-in-raw-context
(lambda (k) (action-instruction A k))))
(do! (action-instruction A)))
;; Does not return to caller; instead, terminates the current actor
;; after sending a link-result to the calling actor.
(define (return! . result-values)
(call-in-raw-context/abort
(lambda () (return-instruction result-values))))
(do/abort! (return-instruction result-values)))
;; Does not return to caller; instead, terminates the current actor
;; without sending a link-result to the calling actor.
(define (return/no-link-result!)
(call-in-raw-context/abort
(lambda () (return-instruction #f))))
(do/abort! (return-instruction #f)))
;; Returns new variables, plus values from spawned actor if any.
(define (spawn! linkage-kind action-fn)
(call-in-raw-context
(lambda (k) (spawn-instruction linkage-kind action-fn k))))
(do! (spawn-instruction linkage-kind action-fn)))
(begin-for-syntax
(define-splicing-syntax-class name
@ -388,10 +364,10 @@
;; callee-id
;; reply-values
;; continuation)
(handle-actor-syscall (transition (struct-copy actor-state s [continuation-table new-table])
'())
(apply continuation
(append reply-values (vector->list (actor-state-variables s))))))
(handle-effects (transition (struct-copy actor-state s [continuation-table new-table]) '())
(lambda (_void)
(apply continuation
(append reply-values (vector->list (actor-state-variables s)))))))
;; ActorState -> Transition
(define (perform-pending-patch s)
@ -410,70 +386,65 @@
;; ActorState Script -> Transition
(define (run-script s script)
(handle-actor-syscall (transition s '())
((reply-to (lambda (dummy)
(define new-variables (script))
(call-in-raw-context/abort
(lambda ()
(script-complete-instruction new-variables)))))
(void))))
(handle-effects (transition s '())
(lambda (_void) (do/abort! (script-complete-instruction (script))))))
(define (actor-body->spawn-action thunk)
(match ((reply-to (lambda (dummy)
(actor (thunk))
(error '%%boot "Reached end of boot thunk")))
(void))
[(spawn-instruction 'actor action-fn _get-next-instr)
(action-fn (gensym 'root-actor) (gensym 'boot-actor))]))
(with-effect #:shallow syndicate-tag k
([(spawn-instruction 'actor action-fn)
(action-fn (gensym 'root-actor) (gensym 'boot-actor))])
(begin (actor (thunk))
(error '%%boot "Reached end of boot thunk"))))
;; Transition Instruction -> Transition
(define (handle-actor-syscall t instr)
(match instr
[(patch-instruction p get-next-instr)
(handle-actor-syscall (sequence-transitions t
(extend-pending-patch *adhoc-label* p))
(get-next-instr (void)))]
[(action-instruction a get-next-instr)
(handle-actor-syscall (sequence-transitions t
perform-pending-patch
(lambda (s) (transition s a)))
(get-next-instr (void)))]
[(return-instruction result-values)
(sequence-transitions t
perform-pending-patch
(lambda (s)
(if result-values
(quit (message (link-result (actor-state-caller-id s)
(actor-state-self-id s)
result-values)))
(quit))))]
[(spawn-instruction linkage-kind action-fn get-next-instr)
(define blocking? (eq? linkage-kind 'call))
(define next-t
(sequence-transitions t
perform-pending-patch
(lambda (s)
(define callee-id (gensym linkage-kind))
(define spawn-action (action-fn callee-id (actor-state-self-id s)))
(transition (if blocking?
(store-continuation s callee-id get-next-instr)
s)
(if (eq? linkage-kind 'dataspace)
(spawn-dataspace spawn-action)
spawn-action)))))
(if blocking?
next-t
(handle-actor-syscall next-t (get-next-instr (void))))]
[(script-complete-instruction new-variables)
(sequence-transitions t
;; NB: Does not perform-pending-patch here.
;; Instead, the script runner will now
;; update ongoing subscriptions and
;; incorporate the pending patch into that
;; process.
(lambda (s)
(transition (struct-copy actor-state s [variables new-variables])
'())))]))
;; Transition (Void -> Instruction) -> Transition
(define (handle-effects t get-this-instr)
(with-effect #:shallow syndicate-tag get-next-instr
([(patch-instruction p)
(handle-effects (sequence-transitions t (extend-pending-patch *adhoc-label* p))
get-next-instr)]
[(action-instruction a)
(handle-effects (sequence-transitions t
perform-pending-patch
(lambda (s) (transition s a)))
get-next-instr)]
[(return-instruction result-values)
(sequence-transitions t
perform-pending-patch
(lambda (s)
(if result-values
(quit (message (link-result (actor-state-caller-id s)
(actor-state-self-id s)
result-values)))
(quit))))]
[(spawn-instruction linkage-kind action-fn)
(define blocking? (eq? linkage-kind 'call))
(define next-t
(sequence-transitions t
perform-pending-patch
(lambda (s)
(define callee-id (gensym linkage-kind))
(define spawn-action
(action-fn callee-id (actor-state-self-id s)))
(transition (if blocking?
(store-continuation s callee-id get-next-instr)
s)
(if (eq? linkage-kind 'dataspace)
(spawn-dataspace spawn-action)
spawn-action)))))
(if blocking?
next-t
(handle-effects next-t get-next-instr))]
[(script-complete-instruction new-variables)
(sequence-transitions t
;; NB: Does not perform-pending-patch here.
;; Instead, the script runner will now
;; update ongoing subscriptions and
;; incorporate the pending patch into that
;; process.
(lambda (s)
(transition (struct-copy actor-state s [variables new-variables])
'())))])
(get-this-instr (void))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Compilation of HLL actors

159
racket/syndicate/effect.rkt Normal file
View File

@ -0,0 +1,159 @@
#lang racket/base
;; Simple effect system.
;; Should `with-effect` be called `with-effect-handler` (or `with-effect-handlers`)?
(provide (except-out (struct-out effect-tag) effect-tag)
make-effect-tag
effect-available?
perform
perform/abort
handle*
with-effect)
(require racket/control)
(require racket/match)
(struct effect-tag (name prompt) #:transparent)
(define (make-effect-tag name) (effect-tag name (make-continuation-prompt-tag name)))
(struct instruction (action k))
(struct result (values))
(define (effect-available? tag)
(continuation-prompt-available? (effect-tag-prompt tag)))
(define (ensure-effect-available! who tag action)
(unless (effect-available? tag)
(error who
"Attempt to invoke action ~v in effect ~a with no handler installed."
action
(effect-tag-name tag))))
(define ((perform tag) action)
(ensure-effect-available! 'perform tag action)
(define p (effect-tag-prompt tag))
(call-with-composable-continuation
(lambda (k) (abort/cc p (lambda () (instruction action k))))
p))
(define ((perform/abort tag) action)
(ensure-effect-available! 'perform/abort tag action)
(abort/cc (effect-tag-prompt tag) (lambda () (instruction action #f))))
(define (handle* shallow? tag body-thunk action-proc result-proc)
(define p (effect-tag-prompt tag))
(let run ((body-thunk body-thunk))
(call-with-values (lambda ()
(call-with-continuation-prompt
(lambda () (call-with-values
body-thunk
(lambda results
(abort/cc p (lambda () (result results))))))
p))
(match-lambda
[(instruction action k)
(action-proc action
(if shallow?
k
(lambda vs
(run (lambda () (apply k vs))))))]
[(result vs)
(apply result-proc vs)]))))
(define-syntax shallow-or-deep
(syntax-rules ()
((shallow-or-deep #:shallow) #t)
((shallow-or-deep #:deep) #f)))
(define-syntax with-effect
(syntax-rules ()
((with-effect sd tag-exp k-var (clause ...) effectful-exp)
(with-effect sd tag-exp k-var (clause ...) effectful-exp #:return values))
((with-effect sd tag-exp k-var (clause ...) effectful-exp #:return result-proc)
(handle* (shallow-or-deep sd)
tag-exp
(lambda () effectful-exp)
(lambda (action k-var) (match action clause ...))
result-proc))))
(module+ test
(require rackunit)
(struct get ())
(struct set (v))
(define cell-effect (make-effect-tag 'cell))
(define do! (perform cell-effect))
(define (with-shallow-cell-effect initial-value thunk)
(let loop ((value initial-value) (thunk thunk))
(with-effect #:shallow cell-effect k
([(get) (loop value (lambda () (k value)))]
[(set v) (loop v (lambda () (k value)))])
(thunk))))
(define (with-deep-cell-effect initial-value thunk)
((with-effect #:deep cell-effect k
([(get) (lambda (s) ((k s) s))]
[(set v) (lambda (s) ((k s) v))])
(thunk)
#:return (lambda (v) (lambda (s) v)))
initial-value))
(define (tracing-cell-effect initial-value thunk)
(struct finish (v))
(let loop ((trace '())
(value initial-value)
(thunk (lambda () ((perform cell-effect) (finish (thunk))))))
(with-effect #:shallow cell-effect k
([(get) (loop (cons `get trace) value (lambda () (k value)))]
[(set v) (loop (cons `(set ,v) trace) v (lambda () (k value)))]
[(finish v) (reverse (cons `(result ,v) trace))])
(thunk))))
(define (tracing-cell-effect2 initial-value thunk)
(let loop ((trace '())
(value initial-value)
(thunk thunk))
(with-effect #:shallow cell-effect k
([(get) (loop (cons `get trace) value (lambda () (k value)))]
[(set v) (loop (cons `(set ,v) trace) v (lambda () (k value)))])
(thunk)
#:return (lambda (v) (reverse (cons `(result ,v) trace))))))
(define (tracing-cell-effect3 initial-value thunk)
(with-shallow-cell-effect '()
(lambda ()
(define final
(let loop ((value initial-value)
(thunk thunk))
(with-effect #:shallow cell-effect k
([(get)
(do! (set (cons `get (do! (get)))))
(loop value (lambda () (k value)))]
[(set v)
(do! (set (cons `(set ,v) (do! (get)))))
(loop v (lambda () (k value)))])
(thunk))))
(reverse (cons `(result ,final) (do! (get)))))))
(define (do-something)
(do! (set (+ (do! (get)) 1)))
(list (do! (get))
(begin (do! (set (+ (do! (get)) 1)))
(do! (get)))))
(check-equal? (with-shallow-cell-effect 0 do-something)
(list 1 2))
(check-equal? (tracing-cell-effect 0 do-something)
`(get (set 1) get get (set 2) get (result ,(list 1 2))))
(check-equal? (tracing-cell-effect2 0 do-something)
`(get (set 1) get get (set 2) get (result ,(list 1 2))))
(check-equal? (tracing-cell-effect3 0 do-something)
`(get (set 1) get get (set 2) get (result ,(list 1 2))))
(check-equal? (with-deep-cell-effect 0 do-something)
(list 1 2)))