"David A. Black" <dblack / wobblini.net>

> > 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:

(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.

(c) what you consider _relevant_ virtual object state is defined by some
very specific transitive traversal of slots (via read-accessors). I'll use
#freeze to illustrate not because it is widespread, but because it gives a
vivid example of the boundary of relavant object state. So a good check
would be how you would define a custom #freeze e.g.

If I implemented Car with @wheel1...@wheel4, I might just use the default
Object#freeze. If I implemented Car with @wheels=[], I might want
    def freeze; super; @wheels.freeze; end

A bit more involved example:
class A
    attr_accessor :a, :b, :c
    def initialize; @a=1; @b = []; @c = [C.new, C.new]; end
    def freeze; super; @b.freeze; @c.freeze; @c[0].freeze; end
    # note: a very custom freeze boundary going out to @c[0]
    # note: it would not make sense to freeze @c[0] without freezing @c
        # that would allow swapping out @c[0], but leaving the original
@c[0] frozen
        # so virtual state should be some transitive traversal of slots
        # House#freeze should not freeze the door knob without freezing the
door
    # note also that (the virtual state of) @c[1] is not frozen.
end

My defintion of #freeze should be aligned with my class-specific notion of
the state of my objects. I visualize this freeze literally as how far out
you want the freeze to propagate, moving outward along slots starting from
obj. Similar thinking goes into my corresponding definitions of hash, deep
copy, equality checks for unit tests, etc.

(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).

> but using [] and an argument.  So calling obj.[] is just a wrapper
> around rand.  An integer is returned, but it isn't saved anywhere.

Right. But that integer is derived from some non-local rng state that is
accessible from rand(). And in your case you chose that your semantics of
obj should not include that state as part of its virtual state e.g. of the
state you would freeze.

> > 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?

Assume x.y.z traverses out two objects from x. Just like my freeze example
in (b), I might want x.freeze to propagate out because I happen to consider
that Xs state must include the z object (Furthermore, I might want to
include just some of the state of the z object that are modified by Z#mutate
.... but that kind of slot-specific freeze is not allowed by Ruby)

> "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.

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? :-)