About Extending Systax

Hi!

A friend of mine challenged me with Smalltalk.
He's a big fan of Smalltalk.
He asked me what I can do if I want to add "try ~ finally ~" systax in
Ruby.
Yes, we already have "begin ~ ensure ~".
But he asked me whether Ruby is flexible enough to extend such a thing
without changing the language itself.
He said that Smalltalk doesn't have "try ~ finally ~" in the language
but can be defined without changing the language.

Personally, I don't think such flexibility is really needed.
However, I want to defend Ruby.
How would you react such an attack?

Disclaimer: This post is not for language flame war but for better
understanding of Ruby.

Thanks.

Sam

Sam Kong wrote:

Hi!

A friend of mine challenged me with Smalltalk.
He's a big fan of Smalltalk.
He asked me what I can do if I want to add "try ~ finally ~" systax in
Ruby.
Yes, we already have "begin ~ ensure ~".
But he asked me whether Ruby is flexible enough to extend such a thing
without changing the language itself.
He said that Smalltalk doesn't have "try ~ finally ~" in the language
but can be defined without changing the language.

Personally, I don't think such flexibility is really needed.
However, I want to defend Ruby.
How would you react such an attack?

There are several ways this could be done ... here is one:

  class TryFinally
    def initialize(block)
      @block = block
    end
    def finally
      @block.call
    ensure
      yield
    end
  end

  def try(&block)
    TryFinally.new(block)
  end

Usage:

  try {
    puts "Trying"
    fail "oops"
  }.finally {
    puts "Always printed"
  }

It is a bit little easier in Smalltalk because of the use of keywords in
an argument list, but still quite doable in Ruby.

···

--
-- Jim Weirich

--
Posted via http://www.ruby-forum.com/.

Dňa Utorok 07 Február 2006 18:53 Sam Kong napísal:

Hi!

A friend of mine challenged me with Smalltalk.
He's a big fan of Smalltalk.
He asked me what I can do if I want to add "try ~ finally ~" systax in
Ruby.
Yes, we already have "begin ~ ensure ~".
But he asked me whether Ruby is flexible enough to extend such a thing
without changing the language itself.
He said that Smalltalk doesn't have "try ~ finally ~" in the language
but can be defined without changing the language.

Personally, I don't think such flexibility is really needed.
However, I want to defend Ruby.
How would you react such an attack?

Well, my first reply would be that noone really understands how the hell
Smalltalk exceptions really work anyway - last time I played around with ST,
I remember an ifCurtailed: method (remembered because I have no idea what
"curtailed" means), some four variants on that one, and then at least two
more basic ifSomething: methods plus variants that had something to do with
exception handling. The second reply would be that custom syntax features are
only marginally useful in production code and tend to be rather confusing.

And of course to top the whole thing off, yes, you can implement something
like custom extension handling syntax.

As to the actual implementation, I can at best think of a solution that wraps
around begin / rescue / ensure - you still have to have some support for
nonlocal exits from the runtime, and ruby doesn't quite let you manipulate
the interpreter at runtime as you can a Smalltalk one. And I don't feel like
learning interpreter hacking just for this example to make myself some low
level access to the interpreter stack.

Yaaanyways, here cometh the (probably incorrect and definately flaky) code:

  def try_catch_finally(try_block, catch_block, finally_block)
    begin
      try_block[]
    rescue => ex
      catch_block[ex]
    ensure
      finally_block[]
    end
  end

  try_catch_finally proc {
    puts "foo"
    raise
  },
  proc { | ex |
    puts "bar"
    puts ex.class.name
  },
  proc {
    puts "quux"
  }

If that's not enough, accuse your friend of being a nitpick :stuck_out_tongue_winking_eye:

Dave Thomas et al have discussed this back in 2001,
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/20991

they use 'ensure' in their solution, but it's an interesting read
anyway.

Sam Kong wrote:

···

Hi!

A friend of mine challenged me with Smalltalk.
He's a big fan of Smalltalk.
He asked me what I can do if I want to add "try ~ finally ~" systax in
Ruby.
Yes, we already have "begin ~ ensure ~".
But he asked me whether Ruby is flexible enough to extend such a thing
without changing the language itself.
He said that Smalltalk doesn't have "try ~ finally ~" in the language
but can be defined without changing the language.

Personally, I don't think such flexibility is really needed.
However, I want to defend Ruby.
How would you react such an attack?

Disclaimer: This post is not for language flame war but for better
understanding of Ruby.

Thanks.

Sam

Quoting David Vallner <david@vallner.net>:

As to the actual implementation, I can at best think of a
solution that wraps around begin / rescue / ensure - you still
have to have some support for nonlocal exits from the runtime,

You could use continutations. Although they're more general, you
can still use them in much the same fashion as you would
setjmp/longjmp in C.

-mental

nice

Jim Weirich wrote:

Sam Kong wrote:

Hi!

[snip]

···

There are several ways this could be done ... here is one:

  class TryFinally
    def initialize(block)
      @block = block
    end
    def finally
      @block.call
    ensure
      yield
    end
  end

  def try(&block)
    TryFinally.new(block)
  end

Usage:

  try {
    puts "Trying"
    fail "oops"
  }.finally {
    puts "Always printed"
  }

It is a bit little easier in Smalltalk because of the use of keywords in an argument list, but still quite doable in Ruby.

--
-- Jim Weirich

Jim Weirich wrote:

Sam Kong wrote:
> Hi!
>
> A friend of mine challenged me with Smalltalk.
> He's a big fan of Smalltalk.
> He asked me what I can do if I want to add "try ~ finally ~" systax in
> Ruby.
> Yes, we already have "begin ~ ensure ~".
> But he asked me whether Ruby is flexible enough to extend such a thing
> without changing the language itself.
> He said that Smalltalk doesn't have "try ~ finally ~" in the language
> but can be defined without changing the language.
>
> Personally, I don't think such flexibility is really needed.
> However, I want to defend Ruby.
> How would you react such an attack?

There are several ways this could be done ... here is one:

  class TryFinally
    def initialize(block)
      @block = block
    end
    def finally
      @block.call
    ensure
      yield
    end
  end

  def try(&block)
    TryFinally.new(block)
  end

Usage:

  try {
    puts "Trying"
    fail "oops"
  }.finally {
    puts "Always printed"
  }

It is a bit little easier in Smalltalk because of the use of keywords in
an argument list, but still quite doable in Ruby.

This is nice.
But my intention is not to make a new syntax that does the same thing.
Let's assume that there's no *ensure* syntax in Ruby.

Sam

Indeed. With continuations you can build your own try/finally that
doesn't use the built in ruby begin/rescue at all:

$cc_stack = []

# takes two procs
def my_try(body, finally)
  if callcc {|cc| $cc_stack << cc}
    body.call
    $cc_stack.pop
  end
  finally.call
end

def my_throw
  $cc_stack.pop.call
end

You could play some tricks to get a nicer syntax, but that's the
general idea.

-Ed

···

On Wed, Feb 08, 2006 at 03:54:39AM +0900, mental@rydia.net wrote:

You could use continutations.

Here's another go at it using continuations:

% cat try_catch_throw.rb
class Flow
   def initialize(parent = nil)
     @exceptions = []
     @parent = parent
     @uncaught_exception = nil
   end
   def try(&block)
     @try_block = block
   end

   def throw(exception)
     caught = @exceptions.each do |key, value|
       if key === exception
         value.call(exception)
         break true
       end
     end

     unless caught == true
       if @parent
         @parent.throw(exception)
       else
         @uncaught_exception = exception
       end
     end
     @cc.call
   end

   def finally(&block)
     @finally = block
     self
   end

   def catch(exception, &block)
      @exceptions << [exception, block]
      self
   end
   def go
    callcc { |@cc| @try_block.call }
    if @finally
     @finally.call
    end
    if @uncaught_exception
      STDERR.puts "Uncaught exception: #{@uncaught_exception}"
      exit(1)
    end
   end

end

% cat test_flow.rb
require 'try_catch_throw'

handling2 = Flow.new
handling2.try {
   handling3 = Flow.new(handling2)
   handling3.try {
     puts "Nested try"
     handling3.throw "ERROR! in handling3"
   }
   handling3.go
}

handling2.catch(String) do |exception|
   puts "handling2 Caught exception: #{exception}"
end

handling2.go

handling = Flow.new

handling.try {
   puts "Hello"
   handling.throw "ERROR!"
   puts "World"
}
handling.finally {
   puts "Finally"
}

handling.go

% ruby test_flow.rb
Nested try
handling2 Caught exception: ERROR! in handling3
Hello
Finally
Uncaught exception: ERROR!

At first I tried to do it without continuations but I couldn't think of a way to do so. Also the nesting is explicit which has the advantage of not using global vars and the disadvantage of excessive typing.