; Engines abstract the notion of timed pre-emption. They were first ; introduced by Haynes and Friedman in Computer Languages, 1987. A ; simpler implementation in which engines are not treated as Scheme ; primitives, but rather are built on top of call/cc was given by ; Dybvig and Hieb in the same journal in 1989. ; (make-engine thunk) creates an engine that will, when applied to ; three arguments, activate a timer-interrupt mechanism and ; evaluate the body of thunk. The arguments given to the engine are: ; ; ticks: a positive integer specifying the amount of "fuel" given to the ; engine. ; return: a procedure that specifies what to do if the evaluation of thunk ; finishes before the fuel expires. Return is a procedure of two ; arguments: amount of fuel "left over" and the ; result of the computation. ; expire: a one-argument procedure to be executed if the computation runs ; out of fuel before the computation completes. The argument that ; will be passed to this procedure is a new engine that can ; finish the computation from where it left off. ; How much computation constitutes a tick? According to the ; introductory paper, a larger tick count is associated with a larger ; expected amount of computation (in a statistical sense), and unbounded ; real time is associated with an unbounded number of ticks (thus any ; looping construct must consume ticks. Chez Scheme essentially counts ; procedure calls. ; Examples are primarily from TSPL and the second paper mentioned above. (load "ooq.ss") (define fib (lambda (n) (cond [(zero? n) 0] [(= n 1) 1] [else (+ (fib (sub1 n)) (fib (- n 2)))]))) (define engine-fib (lambda (n) (make-engine (lambda () (fib n))))) > (define eng (engine-fib 7)) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) > (eng 50 cons (lambda (new-eng) (set! eng new-eng))) (9 . 13) (define mileage ; count the ticks (lambda (thunk) (let loop ([eng (make-engine thunk)] [total-ticks 0]) (eng 50 (lambda (ticks value) (+ total-ticks (- 50 ticks))) (lambda (new-engine) (loop new-engine (+ 50 total-ticks))))))) ; > (mileage (lambda () (fib 10))) ; 1437 ; > (mileage (lambda () (fib 11))) ; 2330 ; > (mileage (lambda () (fib 12))) ; 3775 ; > (mileage (lambda () (fib 13))) ; 6113 ; > (mileage (lambda () (fib 14))) ; 9896 ; earlier version '(define round-robin (let ([snoc (lambda (L x) (append L (list x)))]) (lambda (list-of-engines) (if (null? list-of-engines) '() ((car list-of-engines) 1 ; it's only allowed to run for one tick. (lambda (ticks value) (cons value (round-robin (cdr list-of-engines)))) (lambda (new-engine) (round-robin (snoc (cdr list-of-engines) new-engine)))))))) (define round-robin (lambda (queue-of-engines) (if (queue-of-engines 'empty?) '() (let ([first-engine (queue-of-engines 'dequeue!)]) (first-engine 150 ; it's only allowed to run for one tick. (lambda (ticks value) (cons value (round-robin queue-of-engines))) (lambda (new-engine) (queue-of-engines 'enqueue! new-engine) (round-robin queue-of-engines))))))) (let* ([q (make-queue)] [nums '(3 7 11 4 12 9 2 6 10 8 1 5)] [engine-list (map engine-fib nums)]) (for-each (lambda (eng) (q 'enqueue! eng)) engine-list) (round-robin q)) ; Simulating a multi-tasking operating system (define make-queue (lambda () (cons '() '()))) (define empty-queue? (lambda (q) (eq? (car q) '()))) (define enqueue (lambda (obj q) (let ((x (cons obj '()))) (if (null? (car q)) (set-car! q x) (set-cdr! (cdr q) x)) (set-cdr! q x) q))) (define dequeue (lambda (q) (if (null? (car q)) (error 'dequeue "cannot dequeue from empty queue") (let ((obj (caar q))) (set-car! q (cdar q)) (if (null? (car q)) (set-cdr! q '())) obj)))) (define time-slice (lambda () (add1 (random 100)))) (define kernel (lambda (proc) (define ready-queue (make-queue)) (define start (lambda (proc) (enqueue (make-engine (lambda () (proc trap))) ready-queue))) (define restart (lambda (k v) (enqueue (make-engine (lambda () (k v))) ready-queue))) (define trap (lambda (msg arg) (call/cc (lambda (k) (engine-return (lambda () (case msg (uninterruptible (restart k (arg))) (start-process (start arg) (restart k #f)) (stop-process #f)))))))) (start proc) (let dispatch () (if (empty-queue? ready-queue) 'finished ((dequeue ready-queue) (time-slice) (lambda (ticks trap-handler) (trap-handler) (dispatch)) (lambda (engine) (enqueue engine ready-queue) (dispatch))))))) ; An example that uses this multi-tasking simulator (define amoeba (lambda (generation final) (lambda (trap) (when (< generation final) (trap 'uninterruptible (lambda () (writeout generation))) (trap 'start-process (amoeba (+ generation 1) final)) (trap 'start-process (amoeba (+ generation 1) final))) (trap 'stop-process #f)))) (define writeln (lambda x (for-each display x) (newline))) (define writeout (let ([count 0]) (lambda (n) (display n) (display " ") (set! count (+ count (if (< n 10) 2 3))) (when (>= count 66) (newline) (set! count 0))))) ; > (kernel (amoeba 0 6)) ; 0 1 2 1 2 3 3 3 2 2 4 3 4 3 3 5 4 4 4 3 3 5 4 4 ; 5 4 4 4 5 5 4 4 4 4 5 5 5 5 5 5 5 5 4 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 5 5 5 5 ; 5 finished ; > (kernel (amoeba 0 9)) ; 0 1 2 1 2 2 3 3 2 3 3 3 4 4 3 3 4 4 4 4 4 3 5 4 5 4 4 4 5 5 5 5 5 ; 5 5 4 4 4 4 5 5 6 5 5 5 5 4 6 6 6 6 6 5 5 6 6 6 6 5 6 5 5 5 5 5 5 6 6 6 6 7 ; 6 6 6 6 6 5 6 6 5 5 5 5 7 7 7 7 6 7 6 6 6 6 7 7 6 7 6 6 7 6 6 6 6 6 6 5 6 5 ; 5 7 7 7 7 7 7 7 6 7 6 8 7 7 7 7 7 7 7 7 6 6 7 7 7 6 6 6 6 6 6 6 6 8 8 8 7 8 ; 7 7 8 7 7 7 7 7 8 8 7 7 8 7 7 7 7 7 7 7 6 8 7 7 7 7 7 6 6 6 7 6 6 7 6 6 7 6 ; 6 6 6 8 8 8 7 8 8 8 8 8 8 7 7 8 8 7 7 7 8 7 7 8 8 8 8 8 7 8 7 8 8 8 8 7 8 7 ; 7 7 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 7 6 8 8 8 8 8 8 8 8 8 8 8 8 7 8 8 ; 8 7 8 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 8 7 7 7 8 8 8 8 8 7 8 7 7 7 7 7 7 8 ; 7 7 7 7 7 7 8 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 ; 8 8 7 8 8 8 8 8 8 8 8 8 7 7 8 7 8 8 8 7 8 8 8 8 7 8 8 7 7 7 8 7 7 7 8 7 7 7 ; 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 ; 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 ; 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 8 8 8 8 8 8 8 8 8 8 8 8 7 8 8 8 8 8 8 8 ; 8 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 finished