[#1816] Ruby 1.5.3 under Tru64 (Alpha)? — Clemens Hintze <clemens.hintze@...>

Hi all,

17 messages 2000/03/14

[#1989] English Ruby/Gtk Tutorial? — schneik@...

18 messages 2000/03/17

[#2241] setter() for local variables — ts <decoux@...>

18 messages 2000/03/29

[ruby-talk:02281]

From: mrilu <mrilu@...>
Date: 2000-03-31 01:11:13 UTC
List: ruby-talk #2281
> > On Tue, 28 Mar 2000, Andrew Hunt wrote:
> > 
> > > Well, that brings up an interesting question.  What do
> > > you think about implementing Design By Contract in a 
> > > non-staticaly typed language such as Ruby?  I've toyed around
> > > with several implementations of DBC in Ruby, and once Dave
> > > and I get a bit more of the Ruby book finished I'll look at
> > > it again.
> > > 
> > > But would that be a usefull feature to have in Ruby?  

> > Having looked at this a little, I can see it would be a good idea.

On Thu, 30 Mar 2000 h.fulton@att.net wrote:
> I am trying to implement the best system I can
> *without* changing the language spec...

Me too! I've thought about usefulness of invariants and method conditions
and tried to find imagine why they are more usefull than normal
assertions. I don't know. I guess since they are named a little bit
differently they could be used as a part of the documentation. And there I
can see their usefulness easily.

So after the the discussion on the list started I got more and more
excited. Then I decided to give it a try and try to create Design By
Contract -module.

Here you are, this is the first version:

module DBC
  class DBCError          < StandardError ; end
  class DBCPreError       < DBCError      ; end
  class DBCPostError      < DBCError      ; end
  class DBCInvariantError < DBCError      ; end
  
  def pre
    raise DBCPreError unless yield
  end
  
  def post(klass, method, invariant)
    raise DBCPostError unless yield
    if defined? invariant and 
	klass.public_instance_methods.include? method then
      raise DBCInvariantError unless invariant()
    end
  end

end

And then testing code:

include DBC
class Foo
  @a
  def invariant 
    @a != 3 
  end
  def initialize
    @a = 0
  end
  def bar(b)
    pre { b != 1 }
    @a += b
    print "@a = ", @a, "\n"
    post( Foo, "bar", self) { @a != 2 }
  end
end

for i in (0..4) do
    print "\nTesting foo.bar(#{i})\n"
    foo = Foo.new
    begin
      foo.bar(i)
    rescue DBCPreError
      print "DBCPreError happened as expected\n"
    rescue DBCPostError
      print "DBCPostError happened as expected\n"
    rescue DBCInvariantError
      print "DBCInvariantError happened as expected\n"
    rescue Exception
      print "Some unexpected error happened\n"
    end
end

Produces:

Testing foo.bar(0)
@a = 0

Testing foo.bar(1)
DBCPreError happened as expected

Testing foo.bar(2)
@a = 2
DBCPostError happened as expected

Testing foo.bar(3)
@a = 3
DBCInvariantError happened as expected

Testing foo.bar(4)
@a = 4

> Of course, I am running into problems. But it 
> remains to be seen whether these problems are
> insoluble, or just require more digging in the
> Ruby reference.

I was running into many many problems before this seemed to work or do
something in "right direction" :).

Now I list few:
- how it's possible to access something inside the class from other class?

Current version defines invariant, but that method is public so everybody
can call it. Making it private hides it from post too. I tried to use
instance variables, class variables etc., but this was the first one that
I managed to work. Just to give you some idea, at some point I had:
   Invariant = proc { @a < 3 };

I thought attr, especially attr_reader could be for some help. I rejected
possibility to mix-in DBC, because then the same code would be in exactly
same form in every DBC-using class. What a waste of CPU at compiling, and
memory.

- how to check where we're coming?

I tried to use caller, but it's returning values are basically only for
printing. It returns some strings in weird format like "file.rb:25". I'd
like to have full (read) access to stack. See what was the method where we
came, the associated object, arguments etc.

Basically I'd like to change my code to say post-assertion without need to
"tediously" type calling instance, it's class and method. (Actually now I
noticed that we probably can drop class away, since we should be able to
say something like self.type at post-check.) How to get calling method
automatically? With stack access this should be no problem.

- how to recompile some code efficiently?

Here I would add some code for pre-execute, and post-execute hooks. Since
there is none, I considered to take method declaration and modify it and
then substitute the original code with modifications. At this point I
considered also to wrap function automagically:

class Foo
  def bar
  end
end

def foo_invariant
end

Foo.bar = DBC.check( proc { pretest }, proc {posttest}, foo_invariant )

This check routine replaces so that Foo's bar becomes

def Foo.bar
  DBC.pre Foo
  # original code
  DBC.post Foo
end

I just couldn't produce it. Maybe somebody can help here (too!).


- btw it's tedious (to remember) to type semicolon to
  class DBCError          < StandardError ; end

- many more

It's just way too late for last efficient working day before hacking
weekend :).

      the future is under construction

In This Thread