Showing posts with label continuation. Show all posts
Showing posts with label continuation. Show all posts

2006-11-05

Continuation support in ruby

I heard today that Ruby 2.0 is going to abandon support for continuation, at least initially until things have stabilised a bit. It is no longer a news, it is just that I have not been active in Ruby community for a while.

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)

2006-10-24

System-managed UNWIND-PROTECT in SISC

FORM: (unwind-protect BODYFORM UNWINDFORMS...)
Scheme's call/cc provides a flexible way to control execution flow. Its flexibility allows for different approaches to implementing unwind-protect.

Dorai Sitaram's "Unwind-Protect in Portable Scheme" shows a portable implementation of unwind-protect. It does so by constricting the flexibility of call/cc. User would have to specify when to call the unwindforms part of unwind-protect. In certain situations, it is desirable to have this level of control. In others, it would be akin to having to manually manage memory allocation.

The other approach is having an automatically executed unwindforms such as the one described by Taylor R. Campbell, "2006-05-01 On control brackets and resource release". Having the system manages the execution of unwindforms is more in line to traditional implementations of unwind-protect in other languages. It is this approach that interests me.

By associating a protector cell to the bodyform, we can rely on the GC mechanism cleaning up the protector cell once =bodyform='s continuation is no longer reachable. If it is no longer reachable, then it is safe to execute the unwindforms.

He also describes an optimisation of a common case where =bodyform='s continuation is never reified. Without it, there is only one =bodyform='s continuation and once the execution passes through the end of bodyform, it is safe to continue to unwindforms.

In his 'blog', he provides a sample implementation for MIT-Scheme. The implementation would have to be platform-specific since it hooks into the GC. However, it is easy to port that to other platform.

 

An Implementation in SISC

As a user of SISC, and as someone who has seen many applications (including SISCWeb) using dynamic-wind as a poor-man's unwind-protect, having a system-managed unwind-protect in SISC is a good idea. As I was not aware of any existing implementation, I proceeded to create one using Taylor Campbell's example as the starting point.

To hook into Java's GC finalisation process, I used GCHook.java. Then unwind-protect.scm implements the rest.

unwind-protect-in-sisc


(originally from http://microjet.ath.cx/WebWiki/2006.10.24_UnwindProtectInSISC.html and http://microjet.ath.cx/WebWiki/2006.10.27_UnwindProtectInSISCPart2.html)