Freezing Variable Assignment

  • Thread starter Nicholas Van Weerdenburg
  • Start date
A

Austin Ziegler

On Wed, 8 Dec 2004 01:06:53 +0900, Austin Ziegler

I agree- good interface design effectively creates immutable
classes if desired. But that can be an issue because the object
may only desire immutability later in its lifecycle, or too
certain clients.

I'm still not convinced, and I think that the problem that I'm
seeing is that you're assuming that an object's desire for
immutability is a necessary thing. I don't see such. What I'm trying
to draw out from you is a discussion of the design where you feel
that you need this feature to help me understand why this
fundamental change to the language would be a good one (and, by
extension, help others understand that as well). Alternatively, in
such a discussion, we actually figure out what you want to do with
Ruby and how not to modify Ruby so fundamentally by making your
design more "Ruby"-like.

I'm not claiming to be "all that" as a Ruby developer, but I think
that I've managed to get a fair idea of the feel of good Ruby design
over the last two and a half years :)
Also, good design is an ideal case. In a large project, things can
become ugly and to be able to enforce a high-degree of control via
immutable objects can be beneficial, IMHO. Good design requires
good contracts to enforce it.

Again, I don't necessarily agree. I think that's an absolute
requirement in Java, but I think that Ruby doesn't really require
that.
There are three things that make up an immutable object so far in
our discussion:
1. frozen objects -this could also be done with good interface
design for certain cases. other alternatives to freeze would be
useful to know- wrap/delegate, undeffing, etc.
2. frozen attributes (assignmentFreeze) -mostly appplies to
inheritence, so private instance variables would help here.
3. frozen value objects (referred to objects of a certain type)
-not really discussed yet, but seems important as without it,
freeze is only slushy.

I and others pointed out a fundamental problem with #3 and Ruby --
how do you define type? If you're thinking in terms of Java types,
that's definitely the wrong way to think about type in Ruby. An
object's class is not necessarily it's type in Ruby. (See my
discussion at the end of this email about StrictVar.)
It's hard to describe the benefits of immutables in a short
example, but I think it's similar in nature to global variables-
with mutables, edits can be done in many places, leading to bugs
as well as maintenance issues.

Again, I'm not sure that this is a guaranteed situation in Ruby.
Look at Rails -- trying to implement an immutable in a Rails
application would probably break the application. I developed a bug
tracking application in Perl (and will be reimplementing it in Ruby
at some point) and have never had a need for an immutable. In the
C++ and PL/SQL billing and CRM application I worked on a few years
back, I don't recall any immutable objects, or any problems because
we didn't have them.

[snip]
If an object should protect it's invariant nature, then it would
be nice for ruby to provide some capability for immutable value
objects- maybe something like:
# returns defensive copies
value_reader :name, :location, :value

Not hard:

class << Module
def value_reader(*symbols)
symbols.each do |sym|
self.define_method(sym) do ||
self.instance_variable_get("@{#sym}").dup
end
end
end
end

Untested air code :) You could implement it with
Marshal.load(Marshal.dump(variable-value)) for a deep clone.

I'm not sure that the distinction between value objects and
reference objects is needed -- the distinction isn't present in
Ruby. (Well, it is, but for the most part, it's transparent to you,
the programmer.)
I'm still learning to "think" in Ruby for sure, but the essence of
the need comes from non-trivial systems without a perfect design.

Again, I'm not really sure that this is true for Ruby. I think that
the essence of the need comes from heavyweight Java and EJB designs,
not dynamic languages. As Morpheus said in _The Matrix_, "Free your
mind."
Removing singletons and making certain domain objects immutable is
somewhat like putting aspects of the "Law of Demeter" into place.
http://c2.com/cgi/wiki?LawOfDemeter

I'm willing to engage in a longer discussion on the design you're
seeing for this -- because I think that there are probably other
ways to approach this in a language like Ruby without requiring
changes to the core language that would, IMO, be detrimental.
Another way of thinking about is in terms of object lifecycle- if
it becomes immutable at some point, there is less to worry about.
In a large system, I would freeze and object just prior to
releasing into the "wild"- e.g. outside the boundaries of my
subsystem.

Why? All I have to do to unfreeze your object is dup it. Why limit
what people who may write on top of your classes can do? Certainly,
don't necessarily trust what they provide back to you without
verification, but I don't see any reason to actually do this.
I meant the case where the instance variable @foo is accessed in
side the class B.

Right, but Matz has suggested that B should never access @foo
directly, but should try to do so through the foo method. I don't
see a reason to make @foo itself "private" in the sense that Java
and C++ have private variables.
private_var is a concept?
Yes.

I think private variables would alleviate much of the need.

I don't think so, actually. I would like to see, as I said, a @<foo>
form (it may be different) so that you have privately decorated
instance variables generated by the Ruby parser so that there's no
collision from modules to classes, but ... that's a minor desire.

In earlier messages, you mentioned a "type freeze" for a variable.
There's no reason you can't do that indirectly:

class StrictVar
def initialize(value, &block)
@value = value
@cond = block
end

def cond=(cond)
if @cond
raise ArgumentError
else
@cond = cond
end
end

attr_accessor :value
def value=(val)
if @cond and @cond.cal(val)
@value = val
else
raise TypeError
end
end
end

That, of course, isn't a complete implementation of the delegate
that you'd want to do for that, but you could do an arbitrary block:

a = StrictVar.new("a") { |x| x.kind_of?(String) }

Now, the object referred to by StrictVar will only be able to be
Strings.

-austin
 
F

Florian Gross

Yukihiro said:
|I think this would only make sense when combined with a deep clone. It
|would be nice to have GC's object graph traversal functionality exposed.
|It is a good thing to have something like that available generally.
|
|matz, are you listening?

Yes, I am. I think it's good to have general purpose object graph
traverser. But I'm not going to expose GC marker, for it's tailored
for garbage collection.

Agreed, the GC already knows how to traverse all the built-in classes
(not sure if this is bundled to marking) so maybe that could be factored
out. RData classes would still need to provide a method / callback for
this in any case so this is still a change that needs pondering. Guess
it can wait. :)

Thanks for the quick response.
 
A

Austin Ziegler

Florian Gross said:
itsme213 said:
I was talking about the pure object model part of Ruby. In such
a pure object model, a[1] and a[2] are instance variables (some
Smalltalk descriptions calls these 'indexed instance variables'
as opposed to 'named instance variables'). How something is
optimized in C is a different matter.
Please call this Object state ("as in a[1] and a[2] are part of
Object state" / "@foo is part of Object state") instead. It's a
more general term and seems to already be well-established.
Perhaps.

Though it's hard to say "object x has states a, b" when you mean
"object x has instance variables a, b". Or "object x has states 1,
2, 3" when you mean "object x has indexed variables (indices?) 1,
2, 3".


No, it isn't. It is very easy to say that "object x has a state
of instance variables @a and @b" or "object x has a state of indexes
1, 2, and 3". Frankly, I don't think that a common term is useful or
necessary. Array and Hash objects are fundamentals; everything else
uses instance variables.

-austin
 
A

Austin Ziegler

To each his own, since "easy" and "hard" are subjective :) To me:

"object x has slots @a, @b, 1, 2...5" (assuming "ivars" are
unacceptable in Ruby parlance)
is better than
"object x has states @a, @b, 1, 2...5"
which is better, simpler, and more uniform than
"object x has a state of instance variables @a, @b, or a state
of indices 1, 2..5"

And I'd prefer (e.g. for generic object graph traversal)
Object#each_slot or Object#each_generalized_ivar
over Object#each_state
over Object#each_instance_variable, Array#each_index, Hash#each,
#each_key, #each_value
over Object#each_whatever_an_extension_might_do_in_the_future

Yuck. What you're describing isn't Ruby, and tries to combine things
for some ivory tower intellectual wankery as opposed to the
pragmatism that makes Ruby the language most of us have come to
love.

There is no reason to iterate over an object's instance variables as
a standard feature of the language. It is a conceptual *mistake* to
consider instance variables equivalent to the contents of a Hash or
Array. If you absolutely *must* iterate over instance variables in a
generic object, then use #instance_variables combined with
#instance_variable_[gs]et.

An object has state. Whether that state is comprised of multiple
instance variables, other internal state (e.g., Hash or Array
collections), or both is ultimately irrelevant. If I do the
following:

require 'transaction/simple'
a = %w(a b c d e f g)
a.extend(Transaction::Simple)
a.start_transaction

The object referred to by 'a' has both internal state (the array
values, e.g., self[0..-1]) and several instance variables added by
Transaction::Simple. What is the state of the object? Both the
internal state and the instance variables.
Frankly, I don't think that a common term is useful or necessary.
Array and Hash objects are fundamentals; everything else uses
instance variables.
String.superclass #=> Object
String.instance_variables #=> []
I would find it hard to explain the above using your Hash, Array,
and instance variable. So is there some hidden instance variable
("slot") at work here? What is special about it? Why is it hidden?
Because it is implemented in C? Like Array and Hash? But we'd then
be making implementation distinctions which I was trying to
abstract away.

And I'm telling you that what you're doing is a fruitless exercise
for some ideal of purity that *is not Ruby*. A String has state. It
may *also* have instance variables (see Transaction::Simple) that
contribute to its state. But whether implemented in Ruby, C, or
assembly, it seems highly unlikely that anything will ever be done
to expose the internal state implementation of String, when String
is a fundamental object. What would the instance variable of a
Fixnum be? IMO, that's a silly question -- it is a value.
Most descriptions of an object model choose some term like "slot"
or "instance variable" or something else. I think this would be an
improvement on Ruby's (decent) description of its object model.

And I think that it would be a waste of time, effort, and clarity.
This would do nothing more than muddle Ruby's object model for
people who aren't you.

-austin
 
D

David A. Black

Hi --

Florian Gross said:
itsme213 said:
I was talking about the pure object model part of Ruby. In such a pure
object model, a[1] and a[2] are instance variables (some Smalltalk
descriptions calls these 'indexed instance variables' as opposed to 'named
instance variables'). How something is optimized in C is a different
matter.

Please call this Object state ("as in a[1] and a[2] are part of Object
state" / "@foo is part of Object state") instead. It's a more general
term and seems to already be well-established.

Perhaps.

Though it's hard to say "object x has states a, b" when you mean "object x
has instance variables a, b". Or "object x has states 1, 2, 3" when you
mean "object x has indexed variables (indices?) 1, 2, 3".

Object#each_instance_variable ?
Object#each_state ?
Object#each_slot ?

It's not a big deal, but a common term would be nice.

I don't think there is one that really covers it, though. I tend to
agree with Austin that finding such a term is a solution in search of
a problem (I've never found it unclear to refer to instance variables
as instance variables, array elements as array elements, hash
key/value pairs as hash key/value pairs, etc., rather than trying to
tie it all together) -- but also, consider what Ruby objects can do,
and the ways they are known:

obj = Object.new
def obj.[](x)
rand(100)
end

What is obj's state, as reflected in its indices? Let's ask it:

irb(main):006:0> obj[1]
=> 64
irb(main):007:0> obj[2]
=> 57
irb(main):008:0> obj["hi"]
=> 21

One might be tempted to say: well, those aren't "real" indices. But
that's the thing. Ruby is *fundamentally* built on dynamism and
elastic behaviors of this kind. "Real" indices in Ruby (such as array
indices) are what the indices in my example are: calls to a method.
My example is obviously designed as a kind of puzzle -- but real Ruby
objects masquerade as things, assume various behaviors on the fly,
delegate actions to each other, and so forth, all the time.

This is not to say that my object, above, contains 57 no more or less
than the array [57] contains 57. It contains it less -- but the point
is that Ruby is full of ways to make things like that not matter
and/or be very difficult to establish in the first place.

(And does my object contain 57 less than (0..99).to_a does? :)


David
 
N

Nicholas Van Weerdenburg

Hi Austin,

Thanks for the inputs and code samples. They are very helpful.

I can see your points regarding the Ruby way of doing things. To go
against that might make the language more awkward. And if something
can be code fairly easily, that may weigh against making it a lanuage
feature. Though on the otherhand, over-extending the core language
makes the code more alien to new users.

To my point of view, providing designs that require language support
for contract and interface management is rather impossible- they are
by definition larger systems polluted by design and coding
compromises. Any example I provide will just look trivial and not
advance the discussion. But maybe you can explain how the ruby-way
works for some of the issues below, or why there are not an issue.

One thing I mentioned was "Good design requires good contracts to
enforce it", and you agreed for Java, but not for Ruby. This is hard
for me to understand, based on my assumption that helping control
coupling and cohesion are inportant in any language.

I can accept duck-typing versus static-typing, in general. The usual
perspectives are type-saftey isn't really important, the polymorphic
benefit of duck typing, and the fact that there is no proven
disadvantage.

But, the larger a system, the more important "contracts" or usage and
interaction generally become. To this point, how do you ensure low
coupling in large Ruby systems?

For me this is both encapsulation and interface control. And this
doesn't necessarily mean typing. Interfaces are not necessarily types-
they are allowed messages. So I don't think this is about duck typing.
It's about interface management, and thus coupling-management. Duck
typing actually helps to a certain degree, but an over-exposed public
interface (even if only implied by the responds_to? capability) is
still problematic.

Immutability also speaks to this- it's an interface contract that says
"read-only", limiting side effects. Having experienced the beauty of
making objects immutable and watching the exceptions flare was a
epiphany for me in Java development. The forced restructuring of the
code was excellent- reduced coupling and increased cohesion. So, as a
design pressure, it was very positive. Of course, a good design would
have avoided the need, but that's like saying well written code
doesn't need tests. It was a useful design pressure that achieved good
goals. I haven't seen anything in Ruby that makes me think that it
might not be a good design pressure in Ruby to enforce low coupling
and high cohesion.

I found this recently, and originally wondered "why another pattern",
but then started to appreciate it's higher level of abstraction.
http://www.c2.com/cgi/wiki?ShieldPattern

"A 'Shield' is a phrase in a program that lets you protect some change
decision behind it. There are all sorts of shields in programming -
that's what programming for robustness is all about, which is why
Shield is a pattern that gets invented over and over in different
forms. DontDistinguishBetweenClassesAndInterfaces is the latest
example. Other examples include subroutines, classes, patterns,
frameworks, and the fact that in Eiffel and Self an attribute access
reads the same as a function call."

Much of the discussion regards not just information hiding, but the
difference between interfaces and encapsulation. To me, it's about
encapsulation/information hiding and protocol hiding/management.
"Shield" is a nice metaphor that encompases both.

Without interfaces, how do you control coupling? Delegates, adapters,
facades, and bridges are all forms of protocol management, so I
suppose that would be a way. But that might get complicated.

Though I suppose that without typed variables, it becomes maybe
awkward to "talk" about
interfaces in code anyhow- it's usually stored with the variable.
Maybe wrappers would be the only solution, since variables are
untyped.

I don't know exactly, but Objective-C protocols may be more analogous
to what I talking about then Java interfaces, since I believe
Objective-C has a SmallTalk-type message dispatch mechanism (aka duck
typing).

This brings to mind a concept I came across recently-
Lisp is a great hackers language that maybe doesn't serve "pack
programming" so well due to lack of explicitness in certain areas. I
think it may have been from a Paul Graham essay.

On the other hand, maybe the benefit of a dynamic language makes it
easier to recover fro m coupling, making it less important. Or that
the other benefits are so great, that we just need to buckle down with
better design practices. Or that with unit testing, the low-coupling
design pressure is already there so that interface management is not
an issue- if it was, you would have too many problems unit testing. Or
the traditional theory was crap anyway, and didn't really help with
coupling period- design and unit testing are the only really useful
design pressures.

So to sum up- what aspects of Ruby or Ruby programming practice help
enforce low coupling, high cohesion, and non-rigid architectures?

Any inputs appreciated.

Thanks,
Nick

On Wed, 8 Dec 2004 01:06:53 +0900, Austin Ziegler

I agree- good interface design effectively creates immutable
classes if desired. But that can be an issue because the object
may only desire immutability later in its lifecycle, or too
certain clients.

I'm still not convinced, and I think that the problem that I'm
seeing is that you're assuming that an object's desire for
immutability is a necessary thing. I don't see such. What I'm trying
to draw out from you is a discussion of the design where you feel
that you need this feature to help me understand why this
fundamental change to the language would be a good one (and, by
extension, help others understand that as well). Alternatively, in
such a discussion, we actually figure out what you want to do with
Ruby and how not to modify Ruby so fundamentally by making your
design more "Ruby"-like.

I'm not claiming to be "all that" as a Ruby developer, but I think
that I've managed to get a fair idea of the feel of good Ruby design
over the last two and a half years :)
Also, good design is an ideal case. In a large project, things can
become ugly and to be able to enforce a high-degree of control via
immutable objects can be beneficial, IMHO. Good design requires
good contracts to enforce it.

Again, I don't necessarily agree. I think that's an absolute
requirement in Java, but I think that Ruby doesn't really require
that.


There are three things that make up an immutable object so far in
our discussion:
1. frozen objects -this could also be done with good interface
design for certain cases. other alternatives to freeze would be
useful to know- wrap/delegate, undeffing, etc.
2. frozen attributes (assignmentFreeze) -mostly appplies to
inheritence, so private instance variables would help here.
3. frozen value objects (referred to objects of a certain type)
-not really discussed yet, but seems important as without it,
freeze is only slushy.

I and others pointed out a fundamental problem with #3 and Ruby --
how do you define type? If you're thinking in terms of Java types,
that's definitely the wrong way to think about type in Ruby. An
object's class is not necessarily it's type in Ruby. (See my
discussion at the end of this email about StrictVar.)
It's hard to describe the benefits of immutables in a short
example, but I think it's similar in nature to global variables-
with mutables, edits can be done in many places, leading to bugs
as well as maintenance issues.

Again, I'm not sure that this is a guaranteed situation in Ruby.
Look at Rails -- trying to implement an immutable in a Rails
application would probably break the application. I developed a bug
tracking application in Perl (and will be reimplementing it in Ruby
at some point) and have never had a need for an immutable. In the
C++ and PL/SQL billing and CRM application I worked on a few years
back, I don't recall any immutable objects, or any problems because
we didn't have them.

[snip]
If an object should protect it's invariant nature, then it would
be nice for ruby to provide some capability for immutable value
objects- maybe something like:
# returns defensive copies
value_reader :name, :location, :value

Not hard:

class << Module
def value_reader(*symbols)
symbols.each do |sym|
self.define_method(sym) do ||
self.instance_variable_get("@{#sym}").dup
end
end
end
end

Untested air code :) You could implement it with
Marshal.load(Marshal.dump(variable-value)) for a deep clone.

I'm not sure that the distinction between value objects and
reference objects is needed -- the distinction isn't present in
Ruby. (Well, it is, but for the most part, it's transparent to you,
the programmer.)


I'm still learning to "think" in Ruby for sure, but the essence of
the need comes from non-trivial systems without a perfect design.

Again, I'm not really sure that this is true for Ruby. I think that
the essence of the need comes from heavyweight Java and EJB designs,
not dynamic languages. As Morpheus said in _The Matrix_, "Free your
mind."
Removing singletons and making certain domain objects immutable is
somewhat like putting aspects of the "Law of Demeter" into place.
http://c2.com/cgi/wiki?LawOfDemeter

I'm willing to engage in a longer discussion on the design you're
seeing for this -- because I think that there are probably other
ways to approach this in a language like Ruby without requiring
changes to the core language that would, IMO, be detrimental.
Another way of thinking about is in terms of object lifecycle- if
it becomes immutable at some point, there is less to worry about.
In a large system, I would freeze and object just prior to
releasing into the "wild"- e.g. outside the boundaries of my
subsystem.

Why? All I have to do to unfreeze your object is dup it. Why limit
what people who may write on top of your classes can do? Certainly,
don't necessarily trust what they provide back to you without
verification, but I don't see any reason to actually do this.


I meant the case where the instance variable @foo is accessed in
side the class B.

Right, but Matz has suggested that B should never access @foo
directly, but should try to do so through the foo method. I don't
see a reason to make @foo itself "private" in the sense that Java
and C++ have private variables.
private_var is a concept?
Yes.

I think private variables would alleviate much of the need.

I don't think so, actually. I would like to see, as I said, a @<foo>
form (it may be different) so that you have privately decorated
instance variables generated by the Ruby parser so that there's no
collision from modules to classes, but ... that's a minor desire.

In earlier messages, you mentioned a "type freeze" for a variable.
There's no reason you can't do that indirectly:

class StrictVar
def initialize(value, &block)
@value = value
@cond = block
end

def cond=(cond)
if @cond
raise ArgumentError
else
@cond = cond
end
end

attr_accessor :value
def value=(val)
if @cond and @cond.cal(val)
@value = val
else
raise TypeError
end
end
end

That, of course, isn't a complete implementation of the delegate
that you'd want to do for that, but you could do an arbitrary block:

a = StrictVar.new("a") { |x| x.kind_of?(String) }

Now, the object referred to by StrictVar will only be able to be
Strings.

-austin
 
E

Eric Hodel

Immutability also speaks to this- it's an interface contract that says
"read-only", limiting side effects. Having experienced the beauty of
making objects immutable and watching the exceptions flare was a
epiphany for me in Java development.

I've used unit tests for this more that immutability. Your same
euphoric state can be had with a solid, comprehensive set of unit
tests. (Not to say that immutability can't help.)

In ParseTree/Ruby2C we've over 350 tests and 500 assertions, and any
changes to our interfaces are immediately recognizable by cascades of
failing tests.
The forced restructuring of the
code was excellent- reduced coupling and increased cohesion. So, as a
design pressure, it was very positive.

You can get the same benefit from unit tests.
Of course, a good design would
have avoided the need, but that's like saying well written code
doesn't need tests. It was a useful design pressure that achieved good
goals. I haven't seen anything in Ruby that makes me think that it
might not be a good design pressure in Ruby to enforce low coupling
and high cohesion.

The less coupled my code is, the easier writing unit tests becomes for
me.
 
F

Florian Gross

Eric said:
I've used unit tests for this more that immutability. Your same
euphoric state can be had with a solid, comprehensive set of unit
tests. (Not to say that immutability can't help.)

I agree heavily with this. Specification by testing is a great way of
ensuring that things work. Note that you can also state what things
should *not* be allowed which can be just as important as testing that
things are allowed and work correctly.

Unit testing in general leads you to decoupled components that you can
swap out easily with Mocks (which means that in the real code you will
be able to replace them with similar Objects that use the same
interface) for ensuring that interfaces are used correctly. I'd like to
recommend the "Test-driven development by example" book -- it explains
well how to enforce code quality via tests because it uses tests to
state what should be done before it is done. Whether it does always make
sense to do that is another factor, but it is certainly an interesting
way of looking at Unit Testing.

Another point I'd like to address is integrating specification and
documentation. I'm doing this by embedding simple test cases into the
documentation in the style of sample code. I can extract those and
verify that the samples in the documentations still make sense easily.

Personally, I would also like to see a way of testing whether a given
Object implements a contract (here: passes a suite of unit tests) -- I
think that would be useful for multi method dispatch. (Called method
overloading in the C++/Java world.)

Unit testing and how they can be integrated into development is a very
interesting topic. I think they can be used for much more than they
usually are.
 
N

Nicholas Van Weerdenburg

Eric Hodel wrote:




I agree heavily with this. Specification by testing is a great way of
ensuring that things work. Note that you can also state what things
should *not* be allowed which can be just as important as testing that
things are allowed and work correctly.

Unit testing in general leads you to decoupled components that you can
swap out easily with Mocks (which means that in the real code you will
be able to replace them with similar Objects that use the same
interface) for ensuring that interfaces are used correctly. I'd like to
recommend the "Test-driven development by example" book -- it explains
well how to enforce code quality via tests because it uses tests to
state what should be done before it is done. Whether it does always make
sense to do that is another factor, but it is certainly an interesting
way of looking at Unit Testing.

Another point I'd like to address is integrating specification and
documentation. I'm doing this by embedding simple test cases into the
documentation in the style of sample code. I can extract those and
verify that the samples in the documentations still make sense easily.

Personally, I would also like to see a way of testing whether a given
Object implements a contract (here: passes a suite of unit tests) -- I
think that would be useful for multi method dispatch. (Called method
overloading in the C++/Java world.)

Unit testing and how they can be integrated into development is a very
interesting topic. I think they can be used for much more than they
usually are.

I never though of actually including tests into documentation until I
read you mentioning it a week or two ago. It sounds like a great idea.

Test-driven Development By Example is a great book- especially the
second half. Tests as a continual design pressure to produce good
modular and decoupled code, along with the associated concept of
design as emergent behaviour, are probably the coolest software
development concepts I've encountered since design patterns in 1994. I
wasn't sold on TDD until Beck's book made me see the light of it's
impact on design. Plus seeing design emerge in reverse- evolving
bottom-up from code- gave me a much deeper insight into top-down
design.

I still have a conceptual gap extending it to design at the macro
level (high-level interfaces and coupling) but I can intuitively see
how that might evolve well as emergent design from the design
influence of unit testing. I think I might have a natural inclination
to top-down design, which makes me overly fond of Java-type
interfaces.

Still, how far emergent design can extend into a large system, as well
as how well large, normal-talent teams can collaborate well and
produce a good overall, consistent emergent design, are questions I
think are far from being answered. Especially when considering legacy
code, frameworks, and component libraries. Also, I feel that imposing
contracts to pressure existing designs into successful refactorings is
a powerful technique.

Regards,
Nick
 
G

gabriele renzi

Florian Gross ha scritto:
Personally, I would also like to see a way of testing whether a given
Object implements a contract (here: passes a suite of unit tests) -- I
think that would be useful for multi method dispatch. (Called method
overloading in the C++/Java world.)

+1, even if I think overloading is little different from mmd and I also
believe that predicate dispatch is somewhat cooler :)
 
F

Florian Gross

gabriele said:
Florian Gross ha scritto:


+1, even if I think overloading is little different from mmd and I also
believe that predicate dispatch is somewhat cooler :)

I'm not sure about the specific nuances that the terms cover -- I've so
far only implemented Multi Method Dispatch and it supports arbitrary
predicates as well as typical Class- and Instance-based dispatching.

But even when being able to specify arbitrary lambdas as dispatching
scorers it is still not trivial to use a TestSuite as a contract check.
Not sure if test/unit allows for an easy way of doing that...
 
D

David A. Black

Hi --

David A. Black said:
It's not a big deal, but a common term would be nice.

I don't think there is one that really covers it, though. I tend to
agree with Austin that finding such a term is a solution in search of
a problem (I've never found it unclear to refer to instance variables
as instance variables, array elements as array elements, hash
key/value pairs as hash key/value pairs, etc., rather than trying to
tie it all together) -- but also, consider what Ruby objects can do,
and the ways they are known:

obj = Object.new
def obj.[](x)
rand(100)
end

What is obj's state, as reflected in its indices? Let's ask it:

irb(main):006:0> obj[1]
=> 64
irb(main):007:0> obj[2]
=> 57
irb(main):008:0> obj["hi"]
=> 21

Ah. Thank you for the example.
One might be tempted to say: well, those aren't "real" indices.

Which is, in a way, what I am about to say. But bear with me ...

You are mixing several quite different and usefully separable concerns:
1. What is the local (stored) state of an object (I've used 'slots' for
this)
2. What other non-local (stored) state can be reached via that local state
(transitive on 'slots')
3. What virtual state can be computed in read-only accessor methods via (2),
constants, etc.
4. What stored state (and hence, virtual state 3) is changed by mutators?
What if something that looks like a read-accessor actually mutates stored
(local or non-local) state

I've been talking about (1). Freezing an instance variable, were such a
thing defined, would work on (1).

A family of closely related methods like equality (==), hash(), freeze an
_object_, ... would be about (1) and (2), with a need to behave in ways that
are mutually consistent and that are consistent with the relevant parts of
the virtual state visible via (3).

As soon as you have something like ObjectSpace that is accessible
everywhere, (3) essentially becomes the whole object graph.

Your rand() example is about (4).
rand() has to mutate some stored state somewhere (i.e. some variable,
instance, global, class, C-only, ... ). Your obj can reach and modify that
stored state via rand(). I am guessing obj.rand[10] would affect the outcome
of a subsequent obj2.rand[10] i.e. this random number state is cetainly not
local stored state (1) in obj.

I don't see how or where any state is stored. Are you talking about
the behavior of the random number generator, and the fact that *it*
isn't in the same state after calling obj.[]? If so, I definitely do
not consider that part of the state of obj itself.

Basically I've defined obj.[] as if it were:

def obj.give_me_a_random_number
rand(100)
end

but using [] and an argument. So calling obj.[] is just a wrapper
around rand. An integer is returned, but it isn't saved anywhere.
So my answer would be: obj has no slots corresponding to [1], [2], ....It
does have some non-local slot (somewhere) that is both read and modified by
obj.
Ruby is *fundamentally* built on dynamism and
elastic behaviors of this kind.

I agree. But consider this example:

x = X.new
x.compute #=> 5
x.y.z.mutate 6
x.compute #=> 11
x.freeze
x.y.z.mutate 7 #=> error: can't modify frozen object
# likewise x == x1, x.hash(), someHash[x], etc.

To even mentally understand what is going on, I (and most others, I suspect)
need some conceptual model of state that covers (1), (2), (3), and (4). At
least a mental model; though one in the code could aid testing. Some uniform
notion of "slot" is helpful for this.


I'm afraid I don't mentally understand what's going on in your example
:) How are #y and #z defined?
This is not to say that my object, above, contains 57 no more or less
than the array [57] contains 57. It contains it less -- but the point
is that Ruby is full of ways to make things like that not matter
and/or be very difficult to establish in the first place.

I don't know what you mean by 'contains'. In the core model I am talking
about, an object has slots; in each slot is (i.e. the value of a slot is) a
reference to some other object. I think notions such as 'contains' (quite
possibly related to my freeze example above) can be built on top of this
core model.

"Contains" in the sense that Array#[] shows you what's in the
collection, as does Hash#[]. [] doesn't always index a collection,
even in built-in classes, and certainly doesn't have to; but my point
was that my object acts as if it were a typical collection, using []
to get at elements.
Your object contains some local slots, none of which refers to the object
57. More relevant, even if some of its local slots did refer to the object
57, that reference is a coincidence, and in no way related to what rand()
does.

I'm afraid I'm lost here. My object has neither instance variables
nor stored objects of any kind. Aren't those what you mean by slots?
I'm still not convinced that the conventional terms for these things
stand in need of replacement by a single term -- I prefer that they
not be, in fact -- but I am nonetheless curious exactly what you mean
about this case :)


David
 
D

David A. Black

Hi --

David A. Black said:
Your rand() example is about (4).
rand() has to mutate some stored state somewhere (i.e. some variable,
instance, global, class, C-only, ... ). Your obj can reach and modify that
stored state via rand(). I am guessing obj.rand[10] would affect the outcome
of a subsequent obj2.rand[10] i.e. this random number state is cetainly not
local stored state (1) in obj.

I don't see how or where any state is stored. Are you talking about
the behavior of the random number generator, and the fact that *it*
isn't in the same state after calling obj.[]? If so, I definitely do
not consider that part of the state of obj itself.

And that would be quite reasonable. But my build up to that is: what you
consider virtual object state is specific to your intent, and is derived
ultimately from slots, as follows:

Well... but you're now using this concept of 'slots' uncritically in
connection with Ruby objects, whereas this all began with my
suggesting that the term wasn't really suited. Of course we don't
have to reach accord on this, but I just have to point it out since my
response to quite a lot of this is along those lines :)
(a) obj has no local stored slots that reflect state of the rng (that it has
no local stored slots at all is coincidental)

(b) If you transitively walk all slots from obj, any subset of that space
_could_ be what you consider virtual state of obj.

I don't necessarily consider the object to have a virtual state. I
consider it to have the potential to have state (I could add instance
variable-based state to it dynamically), but as written, it doesn't.
I don't consider the random numbers to be virtual state -- maybe
something that masquerades as state, but that's different (at least I
would view it differently). Virtual state, to me, might evoke state
that is actually held in a delegated object.
(d) There is rng state somewhere, and rand() itself somehow accesses and
manipulates that state. Where that state is, and whether or not you want to
consider it part of the virtual state of obj is entirely specific to the
desired semantics for your obj. It is reasonable for you to not consider the
rng as part of obj state.

On the other hand, I can also conceive of an implementation of rand() that
installs an @rng slot on obj itself, points it at a new
RandomNumberGenerator instance (with its own slots representing the state of
that generator). And I can conceive of situations I would want obj#freeze to
totally freeze the state of that RNG instance as well (or not).

I'm still not buying "installs an @rng slot" as better than "defines
an @rng instance variable" :) But in any case, I'm not sure I can
quite picture it, since rand is a method of Kernel. Without explicit
assignment, I wouldn't want such a method changing the state of my
object. The object's instance variables should be 100% its business.
(I know about instance_variable_[gs]et, but they're really considered
meta-programming-ish and therefore "allowed" to cross otherwise strict
boundaries.)
"Contains" in the sense that Array#[] shows you what's in the
collection, as does Hash#[]. [] doesn't always index a collection,
even in built-in classes, and certainly doesn't have to; but my point
was that my object acts as if it were a typical collection, using []
to get at elements.

So would you use "contains" for anything a method might return? Or would you
limit it to some subset of those things it's slots can get to, directly or
indirectly? I think I read more into it than you meant.

I'm afraid I mean it more as a way of suggesting that the unifying
notion of a 'slot' is not a very good fit for Ruby :)
Sure, [] (or any accessor) can return any stored or derived value, from
colletions or otherwise. Some of them may be part of what you choose to
consider your object state, others not.

The great thing is, though, that it often doesn't matter --
especially with things like delegation, dynamic object extension,
etc., which you can just use without having to decide what their exact
status is with regard to the object's state. I don't mean that you
shouldn't know what's going into your design, but sometimes
terminology just adds an extra layer without much gain. In fact, the
term "state" itself is hardly able to keep up with the life of a Ruby
object (at least a very lively one :)

Come to think of it... that's also my problem with "slot" -- it
strikes me as creating a set of constraints or expectations that Ruby
probably won't follow or match. And in such a case, I consider Ruby
right and the terminology questionable (in the context of Ruby).
Did that make it any better? Or worse? :)

It's an interesting discussion. Actually, even though I didn't say
anything and deleted it, I found the "custom freeze" idea particularly
intriguing. It reminds me obliquely of my desire for a "deep save" in
ActiveRecord, though that's another story.... :)


David
 
N

Nicholas Van Weerdenburg

Hi --

David A. Black said:
Your rand() example is about (4).
rand() has to mutate some stored state somewhere (i.e. some variable,
instance, global, class, C-only, ... ). Your obj can reach and modify that
stored state via rand(). I am guessing obj.rand[10] would affect the outcome
of a subsequent obj2.rand[10] i.e. this random number state is cetainly not
local stored state (1) in obj.

I don't see how or where any state is stored. Are you talking about
the behavior of the random number generator, and the fact that *it*
isn't in the same state after calling obj.[]? If so, I definitely do
not consider that part of the state of obj itself.

And that would be quite reasonable. But my build up to that is: what you
consider virtual object state is specific to your intent, and is derived
ultimately from slots, as follows:

Well... but you're now using this concept of 'slots' uncritically in
connection with Ruby objects, whereas this all began with my
suggesting that the term wasn't really suited. Of course we don't
have to reach accord on this, but I just have to point it out since my
response to quite a lot of this is along those lines :)
(a) obj has no local stored slots that reflect state of the rng (that it has
no local stored slots at all is coincidental)

(b) If you transitively walk all slots from obj, any subset of that space
_could_ be what you consider virtual state of obj.

I don't necessarily consider the object to have a virtual state. I
consider it to have the potential to have state (I could add instance
variable-based state to it dynamically), but as written, it doesn't.
I don't consider the random numbers to be virtual state -- maybe
something that masquerades as state, but that's different (at least I
would view it differently). Virtual state, to me, might evoke state
that is actually held in a delegated object.


(d) There is rng state somewhere, and rand() itself somehow accesses and
manipulates that state. Where that state is, and whether or not you want to
consider it part of the virtual state of obj is entirely specific to the
desired semantics for your obj. It is reasonable for you to not consider the
rng as part of obj state.

On the other hand, I can also conceive of an implementation of rand() that
installs an @rng slot on obj itself, points it at a new
RandomNumberGenerator instance (with its own slots representing the state of
that generator). And I can conceive of situations I would want obj#freeze to
totally freeze the state of that RNG instance as well (or not).

I'm still not buying "installs an @rng slot" as better than "defines
an @rng instance variable" :) But in any case, I'm not sure I can
quite picture it, since rand is a method of Kernel. Without explicit
assignment, I wouldn't want such a method changing the state of my
object. The object's instance variables should be 100% its business.
(I know about instance_variable_[gs]et, but they're really considered
meta-programming-ish and therefore "allowed" to cross otherwise strict
boundaries.)


"Contains" in the sense that Array#[] shows you what's in the
collection, as does Hash#[]. [] doesn't always index a collection,
even in built-in classes, and certainly doesn't have to; but my point
was that my object acts as if it were a typical collection, using []
to get at elements.

So would you use "contains" for anything a method might return? Or would you
limit it to some subset of those things it's slots can get to, directly or
indirectly? I think I read more into it than you meant.

I'm afraid I mean it more as a way of suggesting that the unifying
notion of a 'slot' is not a very good fit for Ruby :)
Sure, [] (or any accessor) can return any stored or derived value, from
colletions or otherwise. Some of them may be part of what you choose to
consider your object state, others not.

The great thing is, though, that it often doesn't matter --
especially with things like delegation, dynamic object extension,
etc., which you can just use without having to decide what their exact
status is with regard to the object's state. I don't mean that you
shouldn't know what's going into your design, but sometimes
terminology just adds an extra layer without much gain. In fact, the
term "state" itself is hardly able to keep up with the life of a Ruby
object (at least a very lively one :)

Come to think of it... that's also my problem with "slot" -- it
strikes me as creating a set of constraints or expectations that Ruby
probably won't follow or match. And in such a case, I consider Ruby
right and the terminology questionable (in the context of Ruby).
Did that make it any better? Or worse? :)

It's an interesting discussion. Actually, even though I didn't say
anything and deleted it, I found the "custom freeze" idea particularly
intriguing. It reminds me obliquely of my desire for a "deep save" in
ActiveRecord, though that's another story.... :)

David

--


David A. Black
(e-mail address removed)

I recall seeing "slots" used in Dylan. Is it used in any other
languages? Is it a common OO concept?

Nick
 
B

Brian Schröder

I recall seeing "slots" used in Dylan. Is it used in any other
languages? Is it a common OO concept?

Nick

Cognition Scientists love models with slots and values. So I imagine lisp would
be a good candidate. But I think it is more some kind of terminology than a
programming/implementation specific feature.

Regards,

Brian
 
B

Brian Schröder

Cognition Scientists love models with slots and values. So I imagine lisp
would be a good candidate. But I think it is more some kind of terminology
than a programming/implementation specific feature.

To clarify: cognition science-people also love lisp.

Brain
 
P

Pit Capitain

Nicholas said:
I recall seeing "slots" used in Dylan. Is it used in any other
languages? Is it a common OO concept?

Slots are a fundamental concept in prototype oriented languages like Self and
Io. I haven't followed this thread in detail, so I can't say whether the term
matches what David and the others were talking about.

Regards,
Pit
 
A

Austin Ziegler

So would you use "contains" for anything a method might return? Or would you
limit it to some subset of those things it's slots can get to, directly or
indirectly? I think I read more into it than you meant.

Sure, [] (or any accessor) can return any stored or derived value, from
colletions or otherwise. Some of them may be part of what you choose to
consider your object state, others not.

Did that make it any better? Or worse? :)

Okay, to back this up:

I don't have a problem with YOUR mental model of an object and its
state, although I don't see objects in such an ivory tower way. I
don't, however, see a need to expose such a thing in Ruby. Remember
that Matz makes things that he doesn't want to be easy "ugly."
Iterating on the individual "slots" of an object would not necessarily
be something to be encouraged.

-austin
 
D

David A. Black

Hi --

I thought "custom freeze" was the heart of what Nicolas started off with.

I think where I came in was discussing the freezing of local variable
bindings, which I didn't (and don't) like because it's such a
different kind of freezing from freezing a specific object (as done by
Kernel#freeze). Which you disagreed with, etc. :)

I certainly don't see any reason not to define custom freeze's for
specific objects, though there might be certain things you'd want to
watch out for. One problem with the "deep freeze" might be that while
objects cannot share instance variables, they can share state by
having i.vars that refer to the same object. So if it's in the
interest of one to freeze that deep object (as opposed to just
freezing the binding), but not in the interest of another, I guess
weird things could happen.

I'm thinking of something like (starting with non-weird case):

class A
attr_accessor :x
def x_add(str)
@x << str
end
end

class B
attr_accessor :y
end

a = A.new
b = B.new

shared = "hi"

a.x = shared
b.y = shared

b.freeze # freezes @y binding, but doesn't freeze @y

a.x_add(" there") # so this works

puts b.y # hi there

Now if you deep-froze b in such a way that @y (the object itself) was
frozen, then a would be in for a surprise when it called x_add.
(That's the "weird" case :) It's probably a fairly marginal
scenario, but it's definitely not impossible.


David
 
A

Austin Ziegler

That's because it would be repetitive to say "instance variable,
array index, hash key or value reference, or some C-accessible
data (like that underlying Class#superclass)" in each place I used
'slot'. And state does not work because state is some aggregation
of instance variables or array indices or hash keys or value
references i.e. some aggregation of slots, and this discussion
needs to refer to _each element_ of that aggregation.

Why? Except for meta-programming, it is not generally the case that
all of this is necessary, and that's where I've objected to your
propositions most of all. I understand that you believe that freeze
should freeze the object and everything it contains, but I don't
agree -- and I suspect *most* folks who use Ruby don't either. Your
custom freeze code appears to be a reasonable way around the issue
at that.
The fact that Ruby is very dynamic e.g. can add instance variables
on the fly, delegate, etc. does not change this. It just means we
need to make some distinctions, which we would need to be aware of
in any case! e.g. We must know if x.freeze_all_instance_variables
(or all array indices, or hash ...) means (a) prohibit change to
all currently defined ivars of x, or (b) prohibit change to all
actual and potential ivars of x

x.freeze means (b), because new instance variables can't be added
after an object is frozen.
But I believe that _is_ what is intended by "freeze", and I think
others comments in this thread bear that out.

I get just the opposite, except from you.

-austin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top