The Problem with Continuation Support in Ruby 1.x
My initial reaction upon hearing that was "What a pity". On second thought, I realised that dropping continuation initially makes sense. The support for continuation in Ruby 1.x is missing an ensure procedure that is continuation-friendly; meaning one that is called only once one a given path is no longer accessible.
$create_cont_k=nil def do_something(k) puts "Do something called" $create_cont_k = k end def do_something_else return unless $create_cont_k puts "Do something else called" k = $create_cont_k $create_cont_k = nil k.call end def create_cont callcc{|create_cont_k| do_something(create_cont_k)} ensure puts "Ensure called" end create_cont do_something_else # /tmp $ ruby1.8 /tmp/ensure-run-multiply.rb # Do something called # Ensure called # Do something else called # Ensure called
A proper ensure mechanism that supports explicit continuation formation system, like Ruby 1.8, would have produced:
# /tmp $ ruby1.8 /tmp/ensure-run-multiply.rb # Do something called # Do something else called # Ensure called
Notice how "ensure" is supposed to be called only once. I have a sample implementation of such continuation-observant ensure mechanism in system-managed-unwind-protect-in-sisc for SISC, a scheme implementation.
"But, but, what if I really want something like ensure that is called each time a code section is called?", you asked. In other words, you want something like Ruby 1.8's multiple-shot ensure in this new world of single-shot ensure.
That is not a problem and don't think that you are being unreasonable for asking this feature. It is useful to be able to guarantee execution of some code upon exiting a dynamic environment.
A dynamic environment is the environment that your program can access at any given time. The environment can change with each instruction executed by the machine.
But such one-sided guarantee does not really do anything interesting. How about a guarantee that some code is also executed upon entering a dynamic environment, thus having a symmetric aspect.
Scheme has dynamic-wind that does the above:
(define *CREATE-CONT-K* #f) (define (do-something k) (display "Do something called\n") (set! *CREATE-CONT-K* k)) (define (do-something-else) (when *CREATE-CONT-K* (display "Do something else called\n") (let ((k *CREATE-CONT-K*)) (set! *CREATE-CONT-K* #f) (k)))) (define (create-cont) (dynamic-wind (lambda () (display "Entering...\n")) (lambda () (call/cc (lambda (create-cont-k) (do-something create-cont-k)))) (lambda () (display "Exiting...\n")))) (create-cont) (do-something-else) ;; /tmp $ sisc -x /tmp/dynwind.scm ;; Entering... ;; Do something called ;; Exiting... ;; Do something else called ;; Entering... ;; Exiting...
The utility in having a in/excursion guard can't be understated. In SRFI-34, dynamic-wind is used to install a custom exception handler. Yes, a custom exception handler; this is probably a strange concept for some people. Why would one have different exception handlers on the same piece of code? The first reason is "why not?". A dynamic language restricted to having a static exception handler feels incomplete. The second reason is more practical. Since you can in/ex-curse from/to any context (via calling a provided continuation object), how an exception is handled in a given context is not necessarily the same as in other different contexts.
Thus, a complete example of how multiple-shot and single-shot ensure example co-existing (sorry, in scheme as there is no Ruby implementation with single-shot ensure yet):
(class-path-extension-append! (list "file:/home/ysantoso/share/project/scheme/unwind-protect/")) (require-library 'unwind-protect) (import unwind-protect) (import s2j) (define (do-gc) ((generic-java-method '|gc|) (java-null (java-class '|java.lang.System|)))) ;; do-something and do-something-else functions are elided for brevity (define (create-cont) (dynamic-wind (lambda () (display "Entering...\n")) (lambda () (unwind-protect (call/cc (lambda (create-cont-k) (do-something create-cont-k))) (display "Ensure\n"))) (lambda () (display "Exiting...\n")))) (create-cont) (do-gc) (do-something-else) (do-gc) ;; /tmp $ sisc -x /tmp/dynwind.scm ;; Entering... ;; Do something called ;; Exiting... ;; Do something else called ;; Entering... ;; Exiting... ;; Ensure
Notice how "ensure" is called just once even though there are two incursion into the protected code block.
The last deficiency in Ruby 1.x is the restriction that a continuation has to be resumed from the same thread that reifies it.
def do_something_else return unless $create_cont_k puts "Do something else called" k = $create_cont_k $create_cont_k = nil t=Thread.new { k.call} t.join end # /tmp $ ruby1.8 ensure-run-multiply.rb # Do something called # Ensure called # Do something else called # ensure-run-multiply.rb:13:in `call': continuation called across threads (RuntimeError) # from ensure-run-multiply.rb:14:in `join' # from ensure-run-multiply.rb:14:in `do_something_else' # from ensure-run-multiply.rb:25
(originally from http://microjet.ath.cx/WebWiki/2006.11.05_Continuation_Support_in_Ruby.html)
No comments:
Post a Comment