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. 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. 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. I've taken the liberty to include some of the
ideas in my code.
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
···
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.
···
--
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
If anything's still unclear: It's not much code, and I've tried to make
it readable.
···
--
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.
--
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. 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.
···
--
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/\.