Declarative relations between object attributes

Some time ago I stumbled over Cells[1], a Common Lisp extension allowing
one
to declare relations between instance variables (called slots in CL);
i.e. a
change to one variable will automatically recompute other variables
depending
on it. I think it's pretty neat, but using it in any interesting context
is
somewhat hampered by the fact that few people go through the
considerable
trouble involved in learning Common Lisp.

Just today it occurred to me that it should be pretty easy to do
something
similar in Ruby. Indeed, a couple of codelines later, I had a Ruby
version of
the standard Cells example (well, a simplified version) running:

class Motor
   cell :temperature, :status
   def initialize
      self.temperature = 0
      calculate :status do
         if self.temperature < 100
            :on
         else
            :off
         end
      end
   end
end
m = Motor.new
m.observe(:temperature) { |old, new| puts "temperature: #{old} ->
#{new}" }
m.observe(:status) { |old, new| puts "status: #{old} -> #{new}" }
m.temperature = 80
m.temperature = 110

=>

temperature: 0 -> 80
temperature: 80 -> 110
status: on -> off

That's certainly not dramatically new; you can do similar things with
the
observer pattern or Qt signals, for example. However, I like the idea of
just
declaring that one variable (status) is a certain function of one or
more
other variables (temperature) and have the library take care of all the
rest.
Makes for cleaner code, particularly if you do model/view programming
(see
model-view.rb in [2]).

I'm not yet sure what to do with this. Possibly similar/better solutions
already exist. Certainly the code needs some work. So I figured that
before
going further I'd try to get some feedback; particularly

* Do you think this is useful?
* Do you know related projects?
* What could be improved?

I've put the code on github[1]. Any input appreciated. Thanks.

Knut

[1] http://common-lisp.net/project/cells/
[2] http://github.com/nome/ruby-cells

···

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

This is a neat bit of code, but is there any chance you could
relicense it under something more permissive, such as MIT?

I wrote a library called observable for use in GUI MVC programming (with FXRuby), something along those lines. One nice feature was pattern matching on the changed value, so you could have separate handlers for different ranges etc. Ruby's #=== methods are very cool and somewhat underused. That might be a nice feature to add to your #observe method.

Here's the code:

http://redshift.sourceforge.net/observable/

Here's my version of your example:

require 'observable'

# Let's just include Observable globally. Conservatively, one would do:

···

On 09/12/2010 02:10 PM, Knut Franke wrote:

Some time ago I stumbled over Cells[1], a Common Lisp extension allowing
one
to declare relations between instance variables (called slots in CL);
i.e. a
change to one variable will automatically recompute other variables
depending
on it. I think it's pretty neat, but using it in any interesting context
is
somewhat hampered by the fact that few people go through the
considerable
trouble involved in learning Common Lisp.

Just today it occurred to me that it should be pretty easy to do
something
similar in Ruby. Indeed, a couple of codelines later, I had a Ruby
version of
the standard Cells example (well, a simplified version) running:

class Motor
    cell :temperature, :status
    def initialize
       self.temperature = 0
       calculate :status do
          if self.temperature< 100
             :on
          else
             :off
          end
       end
    end
end
m = Motor.new
m.observe(:temperature) {|old, new| puts "temperature: #{old} ->
#{new}" }
m.observe(:status) {|old, new| puts "status: #{old} -> #{new}" }
m.temperature = 80
m.temperature = 110

#
# class C
# extend Observable
# include Observable::Match # if desired
# ...
# end

include Observable
include Observable::Match

class Motor
   observable :temperature, :status

   def initialize
     self.temperature = 0
     self.status = :on

     when_temperature 0..100 do
       self.status = :on
     end

     when_temperature 100..100000 do
       self.status = :off
     end
   end
end

m = Motor.new

m.when_temperature CHANGES do |new, old|
   puts "temperature: #{old} -> #{new}"
end

m.when_status CHANGES do |new, old|
   puts "status: #{old} -> #{new}"
end

m.temperature = 80
m.temperature = 110

__END__

temperature: -> 0
status: -> on
temperature: 0 -> 80
temperature: 80 -> 110
status: on -> off

Adam Prescott wrote:

This is a neat bit of code, but is there any chance you could
relicense it under something more permissive, such as MIT?

Yeah, I guess it's a bit pointless to publish this one when you can't
combine it with lots of other code around. I read the MIT license to be
compatible with most if not all free software licenses.

···

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

Joel VanderWerf wrote:

I wrote a library called observable for use in GUI MVC programming (with
FXRuby), something along those lines. One nice feature was pattern
matching on the changed value, so you could have separate handlers for
different ranges etc. Ruby's #=== methods are very cool and somewhat
underused. That might be a nice feature to add to your #observe method.

Here's the code:

Observable

Thanks for the link; I managed to miss that one when trying to google
existing solutions. :slight_smile: I've taken the liberty to include some of the
ideas in my code.

     when_temperature 0..100 do
       self.status = :on
     end

     when_temperature 100..100000 do
       self.status = :off
     end

It appears that for this particular example, your API is nicer than
mine. :wink: Here's another one (distilled from model-view.rb) which
probably better illustrates the idea behind the cells approach:

class Model
   cell :name, :email, :role, :caption
   def initialize
      self.name = "Your Name"
      self.email = "user@example.com"
      self.role = "To"
      calculate(:caption) { "#{self.role}: #{self.name} <#{self.email}>"
}
   end
end

···

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

Sunday, September 12, 2010, 6:19:53 PM, you wrote:

# Let's just include Observable globally. Conservatively, one would do:
#
# class C
# extend Observable
# include Observable::Match # if desired
# ...
# end

I give. What does
  extend Observable
do?

I see in Dave Thomas' *Programming Ruby 1.9* that extend "Adds to obj the instance methods from each module given as a parameter".

So ... what object is "extend Observable" referring to? The class C object? (That is, the class that is C rather than the instance that is C.) Is this related to some singleton stuff?

And how is the extend different than the include?

Ralph

Indeed, being able to mix it in with other code is good!

I noticed you removed your license file from the github repository.
I'm not sure how this works, exactly, but I don't think that undoes
the GPL licensing. Maybe make it explicit with a license notice?

Nice model-view.rb distilled example.

···

On Tue, Sep 14, 2010 at 7:32 AM, Knut Franke <knut.franke@gmx.de> wrote:

Adam Prescott wrote:

This is a neat bit of code, but is there any chance you could
relicense it under something more permissive, such as MIT?

Yeah, I guess it's a bit pointless to publish this one when you can't
combine it with lots of other code around. I read the MIT license to be
compatible with most if not all free software licenses.
--
Posted via http://www.ruby-forum.com/\.

Extend is include on the eigenclass/metaclass/singleton class. In this
case, it's like saying

C.extend Observable

because of the implied "self".

···

On Tue, Sep 14, 2010 at 8:52 PM, Ralph Shnelvar <ralphs@dos32.com> wrote:

Sunday, September 12, 2010, 6:19:53 PM, you wrote:

> # Let's just include Observable globally. Conservatively, one would do:
> #
> # class C
> # extend Observable
> # include Observable::Match # if desired
> # ...
> # end

I give. What does
extend Observable
do?

I see in Dave Thomas' *Programming Ruby 1.9* that extend "Adds to obj the instance methods from each module given as a parameter".

So ... what object is "extend Observable" referring to? The class C object? (That is, the class that is C rather than the instance that is C.) Is this related to some singleton stuff?

And how is the extend different than the include?

Ralph

Thanks for the link; I managed to miss that one when trying to google
existing solutions. :slight_smile: I've taken the liberty to include some of the
ideas in my code.

:slight_smile:

class Model
    cell :name, :email, :role, :caption
    def initialize
       self.name = "Your Name"
       self.email = "user@example.com"
       self.role = "To"
       calculate(:caption) { "#{self.role}: #{self.name}<#{self.email}>"
}
    end
end

I was going to say: why not just do

     def self.caption
       "#{self.role}: #{self.name} <#{self.email}>"
     end

but the calculate method makes it possible to observe(:caption), right?

How does #observe know that caption depends on #role, #name, and #email? It seems difficult to determine (efficiently) whether to call the caption observers when one of the cells changes. The fact that caption depends on role, name, and email (but no others) is embedded opaquely in a proc. Maybe I should just read your code :wink:

···

On 09/13/2010 11:46 PM, Knut Franke wrote:

Adam Prescott wrote:

I noticed you removed your license file from the github repository.
I'm not sure how this works, exactly, but I don't think that undoes
the GPL licensing. Maybe make it explicit with a license notice?

I'm not sure I'm following you there. Make what exactly explicit?
Prohibit people from using the code under GPL? Why would I want to do
that?

Nice model-view.rb distilled example.

Thanks. :slight_smile:

···

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

Joel VanderWerf wrote:

I was going to say: why not just do

     def self.caption
       "#{self.role}: #{self.name} <#{self.email}>"
     end

but the calculate method makes it possible to observe(:caption), right?

Yes, for this example that's the main advantage. More generally,
caption=() could do other interesting stuff; like updating a GUI (see
model-view.rb).

How does #observe know that caption depends on #role, #name, and #email?

By calling the block and keeping track of which cell getters are called
(using a global variable). That's certainly not perfect, but compared to
trying to parse the block content somehow it has the advantage of
working also if the source cells are accessed indirectly via other
methods (not to mention it's much easier to implement).

Maybe I should just read your code :wink:

If anything's still unclear: It's not much code, and I've tried to make
it readable. :slight_smile:

···

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

I mean make the licensing specific. Since there's no longer a license
notice, it's unclear what the copyright status is.

···

On Tue, Sep 14, 2010 at 7:38 PM, Knut Franke <knut.franke@gmx.de> wrote:

Adam Prescott wrote:

I noticed you removed your license file from the github repository.
I'm not sure how this works, exactly, but I don't think that undoes
the GPL licensing. Maybe make it explicit with a license notice?

I'm not sure I'm following you there. Make what exactly explicit?
Prohibit people from using the code under GPL? Why would I want to do
that?

Nice model-view.rb distilled example.

Thanks. :slight_smile:
--
Posted via http://www.ruby-forum.com/\.

Oh, better watch out for evaluations with branches then:

class PublicEmailRole
   ...
  def to_s; ...; end
end

calculate(:caption) do
   case role
   when PublicEmailRole
     "#{self.role}: #{self.name} <#{self.email}>"
   else
     "#{self.role}: #{self.name}"
   end
end

···

On 09/14/2010 02:14 PM, Knut Franke wrote:

Joel VanderWerf wrote:

I was going to say: why not just do

      def self.caption
        "#{self.role}: #{self.name}<#{self.email}>"
      end

but the calculate method makes it possible to observe(:caption), right?

Yes, for this example that's the main advantage. More generally,
caption=() could do other interesting stuff; like updating a GUI (see
model-view.rb).

How does #observe know that caption depends on #role, #name, and #email?

By calling the block and keeping track of which cell getters are called
(using a global variable).

Adam Prescott wrote:

I mean make the licensing specific. Since there's no longer a license
notice, it's unclear what the copyright status is.

Ah, now I got you. :slight_smile: I had included the complete license terms at the
top of cells.rb, replacing the standard reference to the GPL.

I've just added a copy of the license in a separate file; hopfully this
will avoid any confusion about the copyright status. :slight_smile:

···

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

Joel VanderWerf wrote:

Oh, better watch out for evaluations with branches then:

Yes; as I said, it's not perfect (I guess a "perfect" solution just
doesn't exist).

calculate(:caption) do
   case role
   when PublicEmailRole
     "#{self.role}: #{self.name} <#{self.email}>"
   else
     "#{self.role}: #{self.name}"
   end
end

At least it's relatively easy to fix: Just make sure you always
reference all relevant cells. For instance,

calculate(:caption) do
   case role
   when PublicEmailRole
     "#{self.role}: #{self.name} <#{self.email}>"
   else
     self.email # dummy
     "#{self.role}: #{self.name}"
   end
end

···

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

Knut Franke wrote:

Joel VanderWerf wrote:

Oh, better watch out for evaluations with branches then:

[...]

At least it's relatively easy to fix: Just make sure you always
reference all relevant cells. For instance,

I think I've found a better solution. Quite simply, given that changes
to a cell can change which other cells a dependent cells sources, its
dependencies need to be re-evaluated along with its value. Your example
will now just work, without relying on formula blocks to always
reference all cells they'll ever need.

···

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