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.
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
DÅ?a Utorok 07 Február 2006 18:53 Sam K. napÃsal:
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
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.
He said that Smalltalk doesn’t have “try ~ finally ~” in the language @block = block
end
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.
% 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
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.
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.